aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2015-03-02 21:33:10 -0600
committerJoel Martin <github@martintribe.org>2015-03-02 21:33:10 -0600
commit835fb7d8b06e2b44792a97ac89994658bf6d00af (patch)
tree578f67726ab9e3ce5fcbc50220e9761a66c5ddf1
parent6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (diff)
parent8a98ef9a3f3a6b6d05d02dc305a0c886c907e0f3 (diff)
downloadmal-835fb7d8b06e2b44792a97ac89994658bf6d00af.tar.gz
mal-835fb7d8b06e2b44792a97ac89994658bf6d00af.zip
Merge branch 'master' into gh-pages
Conflicts: .gitignore
-rw-r--r--.gitignore59
-rw-r--r--LICENSE.txt387
-rw-r--r--Makefile62
-rw-r--r--README.md284
-rw-r--r--bash/Makefile4
-rw-r--r--bash/core.sh12
-rw-r--r--bash/env.sh10
-rw-r--r--bash/printer.sh26
-rw-r--r--bash/reader.sh10
-rwxr-xr-xbash/step0_repl.sh2
-rwxr-xr-xbash/step1_read_print.sh4
-rwxr-xr-xbash/step2_eval.sh4
-rwxr-xr-xbash/step3_env.sh24
-rwxr-xr-xbash/step4_if_fn_do.sh23
-rwxr-xr-xbash/step5_tco.sh23
-rwxr-xr-xbash/step6_file.sh26
-rwxr-xr-xbash/step7_quote.sh29
-rwxr-xr-xbash/step8_macros.sh40
-rwxr-xr-xbash/step9_try.sh (renamed from bash/step9_interop.sh)60
-rwxr-xr-xbash/stepA_mal.sh (renamed from bash/stepA_more.sh)49
-rw-r--r--bash/tests/stepA_mal.mal (renamed from bash/tests/step9_interop.mal)0
-rw-r--r--bash/types.sh39
-rw-r--r--c/Makefile6
-rw-r--r--c/core.c22
-rw-r--r--c/core.h2
-rw-r--r--c/env.c18
-rw-r--r--c/printer.c21
-rw-r--r--c/reader.c12
-rw-r--r--c/step3_env.c14
-rw-r--r--c/step4_if_fn_do.c17
-rw-r--r--c/step5_tco.c15
-rw-r--r--c/step6_file.c20
-rw-r--r--c/step7_quote.c20
-rw-r--r--c/step8_macros.c29
-rw-r--r--c/step9_try.c (renamed from c/step9_interop.c)53
-rw-r--r--c/stepA_mal.c (renamed from c/stepA_more.c)29
-rw-r--r--c/tests/stepA_mal.mal (renamed from c/tests/step9_interop.mal)0
-rw-r--r--c/types.c8
-rw-r--r--c/types.h7
-rw-r--r--clojure/Makefile2
-rw-r--r--clojure/project.clj4
-rw-r--r--clojure/src/core.clj20
-rw-r--r--clojure/src/readline.clj3
-rw-r--r--clojure/src/step9_try.clj (renamed from clojure/src/step9_interop.clj)22
-rw-r--r--clojure/src/stepA_mal.clj (renamed from clojure/src/stepA_more.clj)2
-rw-r--r--clojure/tests/stepA_mal.mal (renamed from clojure/tests/step9_interop.mal)0
-rw-r--r--coffee/Makefile15
-rw-r--r--coffee/core.coffee89
-rw-r--r--coffee/env.coffee31
-rw-r--r--coffee/node_readline.coffee36
-rw-r--r--coffee/package.json9
-rw-r--r--coffee/printer.coffee25
-rw-r--r--coffee/reader.coffee87
-rw-r--r--coffee/step0_repl.coffee20
-rw-r--r--coffee/step1_read_print.coffee27
-rw-r--r--coffee/step2_eval.coffee52
-rw-r--r--coffee/step3_env.coffee63
-rw-r--r--coffee/step4_if_fn_do.coffee76
-rw-r--r--coffee/step5_tco.coffee82
-rw-r--r--coffee/step6_file.coffee90
-rw-r--r--coffee/step7_quote.coffee106
-rw-r--r--coffee/step8_macros.coffee126
-rw-r--r--coffee/step9_try.coffee134
-rw-r--r--coffee/stepA_mal.coffee142
-rw-r--r--coffee/tests/stepA_mal.mal (renamed from js/tests/step9_interop.mal)0
-rw-r--r--coffee/types.coffee122
-rw-r--r--core.mal15
-rw-r--r--cs/Makefile4
-rw-r--r--cs/core.cs149
-rw-r--r--cs/env.cs14
-rw-r--r--cs/interop.cs66
-rw-r--r--cs/printer.cs4
-rw-r--r--cs/reader.cs8
-rw-r--r--cs/step1_read_print.cs15
-rw-r--r--cs/step2_eval.cs41
-rw-r--r--cs/step3_env.cs51
-rw-r--r--cs/step4_if_fn_do.cs36
-rw-r--r--cs/step5_tco.cs36
-rw-r--r--cs/step6_file.cs57
-rw-r--r--cs/step7_quote.cs57
-rw-r--r--cs/step8_macros.cs75
-rw-r--r--cs/step9_try.cs283
-rw-r--r--cs/stepA_mal.cs (renamed from cs/stepA_more.cs)81
-rw-r--r--cs/types.cs55
-rw-r--r--docs/TODO225
-rw-r--r--docs/step_notes.txt217
-rw-r--r--forth/Makefile10
-rw-r--r--forth/core.fs224
-rw-r--r--forth/env.fs38
-rw-r--r--forth/misc-tests.fs100
-rw-r--r--forth/printer.fs114
-rw-r--r--forth/reader.fs147
-rw-r--r--forth/step0_repl.fs23
-rw-r--r--forth/step1_read_print.fs34
-rw-r--r--forth/step2_eval.fs120
-rw-r--r--forth/step3_env.fs154
-rw-r--r--forth/step4_if_fn_do.fs214
-rw-r--r--forth/step5_tco.fs225
-rw-r--r--forth/step6_file.fs252
-rw-r--r--forth/step7_quote.fs294
-rw-r--r--forth/step8_macros.fs324
-rw-r--r--forth/step9_try.fs381
-rw-r--r--forth/stepA_mal.fs390
-rw-r--r--forth/str.fs73
-rw-r--r--forth/tests/stepA_mal.mal41
-rw-r--r--forth/types.fs624
-rw-r--r--go/Makefile41
-rw-r--r--go/src/core/core.go380
-rw-r--r--go/src/env/env.go59
-rw-r--r--go/src/printer/printer.go62
-rw-r--r--go/src/reader/reader.go162
-rw-r--r--go/src/readline/readline.go70
-rw-r--r--go/src/step0_repl/step0_repl.go42
-rw-r--r--go/src/step1_read_print/step1_read_print.go58
-rw-r--r--go/src/step2_eval/step2_eval.go127
-rw-r--r--go/src/step3_env/step3_env.go155
-rw-r--r--go/src/step4_if_fn_do/step4_if_fn_do.go179
-rw-r--r--go/src/step5_tco/step5_tco.go189
-rw-r--r--go/src/step6_file/step6_file.go208
-rw-r--r--go/src/step7_quote/step7_quote.go241
-rw-r--r--go/src/step8_macros/step8_macros.go282
-rw-r--r--go/src/step9_try/step9_try.go304
-rw-r--r--go/src/stepA_mal/stepA_mal.go306
-rw-r--r--go/src/types/types.go258
-rw-r--r--haskell/Core.hs297
-rw-r--r--haskell/Env.hs65
-rw-r--r--haskell/Makefile31
-rw-r--r--haskell/Printer.hs47
-rw-r--r--haskell/Reader.hs155
-rw-r--r--haskell/Readline.hs32
-rw-r--r--haskell/Types.hs136
-rw-r--r--haskell/step0_repl.hs28
-rw-r--r--haskell/step1_read_print.hs45
-rw-r--r--haskell/step2_eval.hs93
-rw-r--r--haskell/step3_env.hs113
-rw-r--r--haskell/step4_if_fn_do.hs140
-rw-r--r--haskell/step5_tco.hs144
-rw-r--r--haskell/step6_file.hs154
-rw-r--r--haskell/step7_quote.hs183
-rw-r--r--haskell/step8_macros.hs238
-rw-r--r--haskell/step9_try.hs253
-rw-r--r--haskell/stepA_mal.hs255
-rw-r--r--java/Makefile2
-rw-r--r--java/pom.xml4
-rw-r--r--java/src/main/java/mal/core.java38
-rw-r--r--java/src/main/java/mal/env.java15
-rw-r--r--java/src/main/java/mal/printer.java5
-rw-r--r--java/src/main/java/mal/reader.java6
-rw-r--r--java/src/main/java/mal/step3_env.java18
-rw-r--r--java/src/main/java/mal/step4_if_fn_do.java9
-rw-r--r--java/src/main/java/mal/step5_tco.java9
-rw-r--r--java/src/main/java/mal/step6_file.java13
-rw-r--r--java/src/main/java/mal/step7_quote.java13
-rw-r--r--java/src/main/java/mal/step8_macros.java21
-rw-r--r--java/src/main/java/mal/step9_try.java299
-rw-r--r--java/src/main/java/mal/stepA_mal.java (renamed from java/src/main/java/mal/stepA_more.java)23
-rw-r--r--java/src/main/java/mal/types.java5
-rw-r--r--js/Makefile2
-rw-r--r--js/core.js10
-rw-r--r--js/env.js20
-rw-r--r--js/interop.js36
-rw-r--r--js/printer.js8
-rw-r--r--js/reader.js2
-rw-r--r--js/step3_env.js10
-rw-r--r--js/step4_if_fn_do.js4
-rw-r--r--js/step5_tco.js4
-rw-r--r--js/step6_file.js9
-rw-r--r--js/step7_quote.js9
-rw-r--r--js/step8_macros.js15
-rw-r--r--js/step9_try.js (renamed from js/step9_interop.js)32
-rw-r--r--js/stepA_mal.js (renamed from js/stepA_more.js)23
-rw-r--r--js/tests/stepA_mal.mal24
-rw-r--r--js/types.js22
-rw-r--r--js/web/mal.js84
-rw-r--r--lua/Makefile26
-rw-r--r--lua/core.lua228
-rw-r--r--lua/env.lua53
-rw-r--r--lua/printer.lua55
-rw-r--r--lua/reader.lua127
-rw-r--r--lua/readline.lua33
-rwxr-xr-xlua/step0_repl.lua25
-rwxr-xr-xlua/step1_read_print.lua46
-rwxr-xr-xlua/step2_eval.lua79
-rwxr-xr-xlua/step3_env.lua92
-rwxr-xr-xlua/step4_if_fn_do.lua110
-rwxr-xr-xlua/step5_tco.lua118
-rwxr-xr-xlua/step6_file.lua128
-rwxr-xr-xlua/step7_quote.lua154
-rwxr-xr-xlua/step8_macros.lua183
-rwxr-xr-xlua/step9_try.lua203
-rwxr-xr-xlua/stepA_mal.lua205
-rw-r--r--lua/types.lua193
-rw-r--r--lua/utils.lua53
-rw-r--r--make/Makefile7
-rw-r--r--make/core.mk46
-rw-r--r--make/gmsl.mk63
-rw-r--r--make/numbers.mk409
-rw-r--r--make/printer.mk6
-rwxr-xr-xmake/reader.mk16
-rw-r--r--make/step3_env.mk3
-rw-r--r--make/step4_if_fn_do.mk3
-rw-r--r--make/step6_file.mk3
-rw-r--r--make/step7_quote.mk3
-rw-r--r--make/step8_macros.mk3
-rw-r--r--make/step9_try.mk (renamed from make/step9_interop.mk)20
-rw-r--r--make/stepA_mal.mk (renamed from make/stepA_more.mk)3
-rw-r--r--make/tests/stepA_mal.mal (renamed from make/tests/step9_interop.mal)2
-rw-r--r--make/types.mk36
-rw-r--r--make/util.mk31
-rw-r--r--mal/Makefile2
-rw-r--r--mal/core.mal3
-rw-r--r--mal/step9_try.mal178
-rw-r--r--mal/stepA_mal.mal (renamed from mal/stepA_more.mal)0
-rw-r--r--matlab/+types/Atom.m10
-rw-r--r--matlab/+types/Function.m24
-rw-r--r--matlab/+types/HashMap.m47
-rw-r--r--matlab/+types/List.m66
-rw-r--r--matlab/+types/MalException.m11
-rw-r--r--matlab/+types/Nil.m10
-rw-r--r--matlab/+types/Reader.m27
-rw-r--r--matlab/+types/Symbol.m13
-rw-r--r--matlab/+types/Vector.m20
-rw-r--r--matlab/Env.m50
-rw-r--r--matlab/Makefile15
-rw-r--r--matlab/core.m221
-rw-r--r--matlab/printer.m55
-rw-r--r--matlab/reader.m122
-rw-r--r--matlab/step0_repl.m28
-rw-r--r--matlab/step1_read_print.m35
-rw-r--r--matlab/step2_eval.m77
-rw-r--r--matlab/step3_env.m93
-rw-r--r--matlab/step4_if_fn_do.m117
-rw-r--r--matlab/step5_tco.m130
-rw-r--r--matlab/step6_file.m139
-rw-r--r--matlab/step7_quote.m167
-rw-r--r--matlab/step8_macros.m201
-rw-r--r--matlab/step9_try.m224
-rw-r--r--matlab/stepA_mal.m226
l---------matlab/types1
-rw-r--r--matlab/types.m57
-rw-r--r--miniMAL/Makefile12
-rw-r--r--miniMAL/core.json154
-rw-r--r--miniMAL/env.json42
-rw-r--r--miniMAL/miniMAL-core.json111
l---------miniMAL/node_readline.js1
-rw-r--r--miniMAL/package.json8
-rw-r--r--miniMAL/printer.json66
-rw-r--r--miniMAL/reader.json126
-rw-r--r--miniMAL/step0_repl.json21
-rw-r--r--miniMAL/step1_read_print.json27
-rw-r--r--miniMAL/step2_eval.json60
-rw-r--r--miniMAL/step3_env.json74
-rw-r--r--miniMAL/step4_if_fn_do.json92
-rw-r--r--miniMAL/step5_tco.json100
-rw-r--r--miniMAL/step6_file.json107
-rw-r--r--miniMAL/step7_quote.json131
-rw-r--r--miniMAL/step8_macros.json157
-rw-r--r--miniMAL/step9_try.json168
-rw-r--r--miniMAL/stepA_mal.json171
-rw-r--r--miniMAL/types.json145
-rw-r--r--ocaml/Makefile37
-rw-r--r--ocaml/core.ml206
-rw-r--r--ocaml/env.ml33
-rw-r--r--ocaml/printer.ml38
-rw-r--r--ocaml/reader.ml111
-rw-r--r--ocaml/step0_repl.ml23
-rw-r--r--ocaml/step1_read_print.ml15
-rw-r--r--ocaml/step2_eval.ml64
-rw-r--r--ocaml/step3_env.ml73
-rw-r--r--ocaml/step4_if_fn_do.ml83
l---------ocaml/step5_tco.ml1
-rw-r--r--ocaml/step6_file.ml95
-rw-r--r--ocaml/step7_quote.ml110
-rw-r--r--ocaml/step8_macros.ml144
-rw-r--r--ocaml/step9_try.ml156
-rw-r--r--ocaml/stepA_mal.ml159
-rw-r--r--ocaml/types.ml50
-rw-r--r--perf.mal27
-rw-r--r--perl/Makefile2
-rw-r--r--perl/core.pm25
-rw-r--r--perl/env.pm8
-rw-r--r--perl/interop.pm1
-rw-r--r--perl/printer.pm5
-rw-r--r--perl/reader.pm4
-rw-r--r--perl/readline.pm31
-rw-r--r--perl/step0.5_repl.pl33
-rw-r--r--perl/step0_repl.pl6
-rw-r--r--perl/step1_read_print.pl8
-rw-r--r--perl/step2_eval.pl8
-rw-r--r--perl/step3_env.pl22
-rw-r--r--perl/step4_if_fn_do.pl18
-rw-r--r--perl/step5_tco.pl18
-rw-r--r--perl/step6_file.pl23
-rw-r--r--perl/step7_quote.pl23
-rw-r--r--perl/step8_macros.pl34
-rw-r--r--perl/step9_try.pl (renamed from perl/step9_interop.pl)62
-rw-r--r--perl/stepA_mal.pl (renamed from perl/stepA_more.pl)31
-rw-r--r--perl/tests/stepA_mal.mal (renamed from perl/tests/step9_interop.mal)0
-rw-r--r--perl/types.pm13
-rw-r--r--php/Makefile2
-rw-r--r--php/core.php8
-rw-r--r--php/env.php8
-rw-r--r--php/printer.php4
-rw-r--r--php/reader.php2
-rw-r--r--php/step3_env.php14
-rw-r--r--php/step4_if_fn_do.php8
-rw-r--r--php/step5_tco.php8
-rw-r--r--php/step6_file.php12
-rw-r--r--php/step7_quote.php12
-rw-r--r--php/step8_macros.php20
-rw-r--r--php/step9_try.php (renamed from php/stepA_more.php)24
-rw-r--r--php/stepA_mal.php (renamed from php/step9_interop.php)54
-rw-r--r--php/tests/stepA_mal.mal25
-rw-r--r--php/types.php7
-rw-r--r--process/guide.md1211
-rw-r--r--process/step0_repl.gliffy1
-rw-r--r--process/step0_repl.pngbin0 -> 30283 bytes
-rw-r--r--process/step0_repl.txt11
-rw-r--r--process/step1_read_print.gliffy1
-rw-r--r--process/step1_read_print.pngbin0 -> 36159 bytes
-rw-r--r--process/step1_read_print.txt14
-rw-r--r--process/step2_eval.gliffy1
-rw-r--r--process/step2_eval.pngbin0 -> 49461 bytes
-rw-r--r--process/step2_eval.txt25
-rw-r--r--process/step3_env.gliffy1
-rw-r--r--process/step3_env.pngbin0 -> 56202 bytes
-rw-r--r--process/step3_env.txt38
-rw-r--r--process/step4_if_fn_do.gliffy1
-rw-r--r--process/step4_if_fn_do.pngbin0 -> 61854 bytes
-rw-r--r--process/step4_if_fn_do.txt70
-rw-r--r--process/step5_tco.gliffy1
-rw-r--r--process/step5_tco.pngbin0 -> 63238 bytes
-rw-r--r--process/step5_tco.txt72
-rw-r--r--process/step6_file.gliffy1
-rw-r--r--process/step6_file.pngbin0 -> 65284 bytes
-rw-r--r--process/step6_file.txt79
-rw-r--r--process/step7_quote.gliffy1
-rw-r--r--process/step7_quote.pngbin0 -> 67610 bytes
-rw-r--r--process/step7_quote.txt86
-rw-r--r--process/step8_macros.gliffy1
-rw-r--r--process/step8_macros.pngbin0 -> 73104 bytes
-rw-r--r--process/step8_macros.txt100
-rw-r--r--process/step9_try.gliffy1
-rw-r--r--process/step9_try.pngbin0 -> 84921 bytes
-rw-r--r--process/step9_try.txt102
-rw-r--r--process/step9_try2.txt133
-rw-r--r--process/stepA_mal.gliffy1
-rw-r--r--process/stepA_mal.pngbin0 -> 83168 bytes
-rw-r--r--process/stepA_mal.txt136
-rw-r--r--ps/Makefile2
-rw-r--r--ps/core.ps20
-rw-r--r--ps/interop.ps21
-rw-r--r--ps/printer.ps25
-rw-r--r--ps/reader.ps32
-rw-r--r--ps/step1_read_print.ps7
-rw-r--r--ps/step2_eval.ps7
-rw-r--r--ps/step3_env.ps9
-rw-r--r--ps/step4_if_fn_do.ps11
-rw-r--r--ps/step5_tco.ps11
-rw-r--r--ps/step6_file.ps11
-rw-r--r--ps/step7_quote.ps11
-rw-r--r--ps/step8_macros.ps11
-rw-r--r--ps/step9_try.ps (renamed from ps/step9_interop.ps)61
-rw-r--r--ps/stepA_mal.ps (renamed from ps/stepA_more.ps)15
-rw-r--r--ps/tests/stepA_mal.mal23
-rw-r--r--ps/types.ps23
-rw-r--r--python/Makefile2
-rw-r--r--python/core.py10
-rw-r--r--python/mal_types.py19
-rw-r--r--python/printer.py4
-rw-r--r--python/reader.py3
-rw-r--r--python/step3_env.py8
-rw-r--r--python/step4_if_fn_do.py2
-rw-r--r--python/step5_tco.py2
-rw-r--r--python/step6_file.py6
-rw-r--r--python/step7_quote.py6
-rw-r--r--python/step8_macros.py6
-rw-r--r--python/step9_try.py (renamed from python/step9_interop.py)23
-rw-r--r--python/stepA_mal.py (renamed from python/stepA_more.py)6
-rw-r--r--r/Makefile24
-rw-r--r--r/core.r182
-rw-r--r--r/env.r42
-rw-r--r--r/printer.r54
-rw-r--r--r/reader.r137
-rw-r--r--r/readline.r40
-rw-r--r--r/step0_repl.r27
-rw-r--r--r/step1_read_print.r32
-rw-r--r--r/step2_eval.r63
-rw-r--r--r/step3_env.r81
-rw-r--r--r/step4_if_fn_do.r100
-rw-r--r--r/step5_tco.r108
-rw-r--r--r/step6_file.r120
-rw-r--r--r/step7_quote.r148
-rw-r--r--r/step8_macros.r178
-rw-r--r--r/step9_try.r196
-rw-r--r--r/stepA_mal.r198
-rw-r--r--r/types.r159
-rw-r--r--racket/Makefile12
-rw-r--r--racket/core.rkt101
-rw-r--r--racket/env.rkt47
-rw-r--r--racket/printer.rkt44
-rw-r--r--racket/reader.rkt85
-rw-r--r--racket/readline.rkt32
-rwxr-xr-xracket/step0_repl.rkt27
-rwxr-xr-xracket/step1_read_print.rkt30
-rwxr-xr-xracket/step2_eval.rkt49
-rwxr-xr-xracket/step3_env.rkt61
-rwxr-xr-xracket/step4_if_fn_do.rkt82
-rwxr-xr-xracket/step5_tco.rkt91
-rwxr-xr-xracket/step6_file.rkt97
-rwxr-xr-xracket/step7_quote.rkt119
-rwxr-xr-xracket/step8_macros.rkt143
-rwxr-xr-xracket/step9_try.rkt160
-rwxr-xr-xracket/stepA_mal.rkt163
-rw-r--r--racket/types.rkt110
-rw-r--r--ruby/Makefile2
-rw-r--r--ruby/core.rb12
-rw-r--r--ruby/mal_readline.rb2
-rw-r--r--ruby/printer.rb6
-rw-r--r--ruby/reader.rb6
-rw-r--r--ruby/step0_repl.rb3
-rw-r--r--ruby/step1_read_print.rb9
-rw-r--r--ruby/step2_eval.rb9
-rw-r--r--ruby/step3_env.rb11
-rw-r--r--ruby/step4_if_fn_do.rb13
-rw-r--r--ruby/step5_tco.rb13
-rw-r--r--ruby/step6_file.rb13
-rw-r--r--ruby/step7_quote.rb13
-rw-r--r--ruby/step8_macros.rb13
-rw-r--r--ruby/step9_try.rb (renamed from ruby/step9_interop.rb)30
-rw-r--r--ruby/stepA_mal.rb (renamed from ruby/stepA_more.rb)19
-rw-r--r--ruby/tests/stepA_mal.mal27
-rw-r--r--ruby/types.rb2
-rwxr-xr-xruntest-old.py134
-rwxr-xr-xruntest.py111
-rw-r--r--rust/Cargo.toml39
-rw-r--r--rust/Makefile36
-rw-r--r--rust/src/core.rs561
-rw-r--r--rust/src/env.rs118
-rw-r--r--rust/src/printer.rs45
-rw-r--r--rust/src/reader.rs213
-rw-r--r--rust/src/readline.rs76
-rw-r--r--rust/src/step0_repl.rs25
-rw-r--r--rust/src/step1_read_print.rs52
-rw-r--r--rust/src/step2_eval.rs129
-rw-r--r--rust/src/step3_env.rs204
-rw-r--r--rust/src/step4_if_fn_do.rs235
-rw-r--r--rust/src/step5_tco.rs257
-rw-r--r--rust/src/step6_file.rs293
-rw-r--r--rust/src/step7_quote.rs352
-rw-r--r--rust/src/step8_macros.rs447
-rw-r--r--rust/src/step9_try.rs477
-rw-r--r--rust/src/stepA_mal.rs479
-rw-r--r--rust/src/types.rs405
-rw-r--r--scala/Makefile20
-rw-r--r--scala/build.sbt15
-rw-r--r--scala/core.scala265
-rw-r--r--scala/env.scala42
-rw-r--r--scala/printer.scala43
-rw-r--r--scala/reader.scala90
-rw-r--r--scala/step0_repl.scala33
-rw-r--r--scala/step1_read_print.scala39
-rw-r--r--scala/step2_eval.scala73
-rw-r--r--scala/step3_env.scala89
-rw-r--r--scala/step4_if_fn_do.scala110
-rw-r--r--scala/step5_tco.scala121
-rw-r--r--scala/step6_file.scala130
-rw-r--r--scala/step7_quote.scala163
-rw-r--r--scala/step8_macros.scala207
-rw-r--r--scala/step9_try.scala227
-rw-r--r--scala/stepA_mal.scala229
-rw-r--r--scala/types.scala202
-rw-r--r--tests/incA.mal3
-rw-r--r--tests/incB.mal4
-rw-r--r--tests/incC.mal6
-rw-r--r--tests/perf1.mal1
-rw-r--r--tests/perf2.mal1
-rw-r--r--tests/perf3.mal28
-rw-r--r--tests/step1_read_print.mal80
-rw-r--r--tests/step2_eval.mal6
-rw-r--r--tests/step3_env.mal6
-rw-r--r--tests/step4_if_fn_do.mal33
-rw-r--r--tests/step6_file.mal29
-rw-r--r--tests/step7_quote.mal63
-rw-r--r--tests/step8_macros.mal123
-rw-r--r--tests/step9_try.mal (renamed from tests/stepA_more.mal)220
-rw-r--r--tests/test.txt1
-rw-r--r--vb/Makefile52
-rw-r--r--vb/core.vb457
-rw-r--r--vb/env.vb55
-rw-r--r--vb/getline.cs1089
-rw-r--r--vb/printer.vb52
-rw-r--r--vb/reader.vb183
-rw-r--r--vb/readline.vb32
-rw-r--r--vb/step0_repl.vb43
-rw-r--r--vb/step1_read_print.vb59
-rw-r--r--vb/step2_eval.vb134
-rw-r--r--vb/step3_env.vb155
-rw-r--r--vb/step4_if_fn_do.vb188
-rw-r--r--vb/step5_tco.vb197
-rw-r--r--vb/step6_file.vb215
-rw-r--r--vb/step7_quote.vb248
-rw-r--r--vb/step8_macros.vb288
-rw-r--r--vb/step9_try.vb315
-rw-r--r--vb/stepA_mal.vb317
-rw-r--r--vb/types.vb460
506 files changed, 39718 insertions, 1608 deletions
diff --git a/.gitignore b/.gitignore
index 5cdd21b..6c346cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,50 @@
*/experiments
make/mal.mk
+miniMAL/node_modules
js/node_modules
js/mal.js
+js/mal_web.js
+coffee/node_modules
bash/mal.sh
c/*.o
*.pyc
-c/mal
-c/step0_repl
-c/step1_read_print
-c/step2_eval
-c/step3_env
-c/step4_if_fn_do
-c/step5_tco
-c/step6_file
-c/step7_quote
-c/step8_macros
-c/step9_interop
-c/stepA_more
+*/mal
+*/step0_repl
+*/step1_read_print
+*/step2_eval
+*/step3_env
+*/step4_if_fn_do
+*/step5_tco
+*/step6_file
+*/step7_quote
+*/step8_macros
+*/step9_try
+*/stepA_mal
cs/*.exe
cs/*.dll
cs/*.mdb
clojure/target
clojure/.lein-repl-history
+go/step*
+go/mal
java/target/
java/dependency-reduced-pom.xml
-rust/step0_repl
-rust/step1_read_print
-rust/step2_eval
-rust/step3_env
-rust/step4_if_fn_do
-rust/step5_tco
-rust/step6_file
-rust/step7_quote
-rust/step8_macros
-rust/step9_interop
-rust/stepA_more
+ocaml/*.cmi
+ocaml/*.cmo
+ocaml/*.swp
+ocaml/*.cmx
+ocaml/*.o
+ocaml/mal_lib.*
+rust/target/
+rust/mal
+rust/Cargo.lock
+rust/.cargo
+r/lib
+vb/*.exe
+vb/*.dll
+scala/target
+scala/project
+haskell/*.hi
+haskell/*.o
+lua/lib
+lua/linenoise.so
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..3bfdf5f
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,387 @@
+Copyright (C) 2015 Joel Martin <github@martintribe.org>
+
+Mal (make-a-lisp) is licensed under the MPL 2.0 (Mozilla Public
+License 2.0). The text of the MPL 2.0 license is included below and
+can be found at https://www.mozilla.org/MPL/2.0/
+
+Many of the implemenations run or compile using a line editing
+library. In some cases, the implementations provide an option in the
+code to switch between the GNU GPL licensed GNU readline library and
+the BSD licensed editline (libedit) library.
+
+
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
+
diff --git a/Makefile b/Makefile
index ec52ffc..03ed5f8 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,9 @@ PYTHON = python
# Settings
#
-IMPLS = bash c clojure cs java js make mal perl php ps python ruby
+IMPLS = bash c clojure coffee cs forth go haskell java js lua make mal \
+ ocaml matlab miniMAL perl php ps python r racket ruby rust \
+ scala vb
step0 = step0_repl
step1 = step1_read_print
@@ -21,21 +23,27 @@ step5 = step5_tco
step6 = step6_file
step7 = step7_quote
step8 = step8_macros
-step9 = step9_interop
-stepA = stepA_more
+step9 = step9_try
+stepA = stepA_mal
EXCLUDE_TESTS += test^bash^step5 # no stack exhaustion or completion
EXCLUDE_TESTS += test^c^step5 # segfault
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
EXCLUDE_TESTS += test^mal^step5 # no TCO capability/step
+EXCLUDE_TESTS += test^go^step5 # test completes, even at 100,000
EXCLUDE_TESTS += test^php^step5 # test completes, even at 100,000
+EXCLUDE_TESTS += test^racket^step5 # test completes
EXCLUDE_TESTS += test^ruby^step5 # test completes, even at 100,000
+EXCLUDE_TESTS += test^rust^step5 # no catching stack overflows
+EXCLUDE_TESTS += test^ocaml^step5 # test completes, even at 1,000,000
# interop tests now implemented yet
-EXCLUDE_TESTS += test^cs^step9 test^java^step9 test^mal^step9 \
- test^php^step9 test^ps^step9 test^python^step9 \
- test^ruby^step9
+EXCLUDE_TESTS += test^cs^stepA test^go^stepA test^haskell^stepA \
+ test^java^stepA test^mal^stepA test^mal^step0 \
+ test^php^stepA test^ps^stepA test^python^stepA \
+ test^ruby^stepA test^rust^stepA test^vb^stepA
EXCLUDE_PERFS = perf^mal # TODO: fix this
@@ -48,35 +56,67 @@ STEP_TEST_FILES = $(strip $(wildcard $(1)/tests/$($(2)).mal) $(wildcard tests/$(
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
cs_STEP_TO_PROG = cs/$($(1)).exe
+forth_STEP_TO_PROG = forth/$($(1)).fs
+go_STEP_TO_PROG = go/$($(1))
java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java
+haskell_STEP_TO_PROG = haskell/$($(1))
js_STEP_TO_PROG = js/$($(1)).js
+lua_STEP_TO_PROG = lua/$($(1)).lua
make_STEP_TO_PROG = make/$($(1)).mk
mal_STEP_TO_PROG = mal/$($(1)).mal
+ocaml_STEP_TO_PROG = ocaml/$($(1))
+matlab_STEP_TO_PROG = matlab/$($(1)).m
+miniMAL_STEP_TO_PROG = miniMAL/$($(1)).json
perl_STEP_TO_PROG = perl/$($(1)).pl
php_STEP_TO_PROG = php/$($(1)).php
ps_STEP_TO_PROG = ps/$($(1)).ps
python_STEP_TO_PROG = python/$($(1)).py
+r_STEP_TO_PROG = r/$($(1)).r
+racket_STEP_TO_PROG = racket/$($(1)).rkt
ruby_STEP_TO_PROG = ruby/$($(1)).rb
+rust_STEP_TO_PROG = rust/target/$($(1))
+scala_STEP_TO_PROG = scala/$($(1)).scala
+vb_STEP_TO_PROG = vb/$($(1)).exe
+# Needed some argument munging
+COMMA = ,
+noop =
+SPACE = $(noop) $(noop)
bash_RUNSTEP = bash ../$(2) $(3)
c_RUNSTEP = ../$(2) $(3)
clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3)
+coffee_RUNSTEP = coffee ../$(2) $(3)
cs_RUNSTEP = mono ../$(2) --raw $(3)
+forth_RUNSTEP = gforth ../$(2) $(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),)"
js_RUNSTEP = node ../$(2) $(3)
+lua_RUNSTEP = ../$(2) --raw $(3)
make_RUNSTEP = make -f ../$(2) $(3)
mal_RUNSTEP = $(call $(MAL_IMPL)_RUNSTEP,$(1),$(call $(MAL_IMPL)_STEP_TO_PROG,stepA),../$(2),") #"
-perl_RUNSTEP = perl ../$(2) $(3)
+ocaml_RUNSTEP = ../$(2) $(3)
+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)
+perl_RUNSTEP = perl ../$(2) --raw $(3)
php_RUNSTEP = php ../$(2) $(3)
-ps_RUNSTEP = $(4)gs -q -dNODISPLAY -- ../$(2) $(3)$(4)
+ps_RUNSTEP = $(4)gs -q -I./ -dNODISPLAY -- ../$(2) $(3)$(4)
python_RUNSTEP = $(PYTHON) ../$(2) $(3)
+r_RUNSTEP = Rscript ../$(2) $(3)
+racket_RUNSTEP = ../$(2) $(3)
ruby_RUNSTEP = ruby ../$(2) $(3)
+rust_RUNSTEP = ../$(2) $(3)
+scala_RUNSTEP = sbt 'run-main $($(1))$(if $(3), $(3),)'
+vb_RUNSTEP = mono ../$(2) --raw $(3)
# Extra options to pass to runtest.py
-cs_TEST_OPTS = --redirect
+cs_TEST_OPTS = --mono
mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
+vb_TEST_OPTS = --mono
# Derived lists
@@ -156,5 +196,7 @@ $(IMPL_PERF):
echo 'Running: $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf1.mal)'; \
$(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf1.mal); \
echo 'Running: $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf2.mal)'; \
- $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf2.mal))
+ $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf2.mal); \
+ echo 'Running: $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf3.mal)'; \
+ $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf3.mal))
diff --git a/README.md b/README.md
index 01e8153..7e759e1 100644
--- a/README.md
+++ b/README.md
@@ -2,42 +2,57 @@
## Description
-Mal is an interpreter for a subset of the Clojure programming
-language. Mal is implemented from scratch in 13 different languages:
+Mal is a Clojure inspired Lisp interpreter.
+
+Mal is implemented in 26 different languages:
* Bash shell
* C
* C#
* Clojure
+* CoffeeScript
+* Forth
+* Go
+* Haskell
* Java
-* Javascript
+* Javascript ([Online Demo](http://kanaka.github.io/mal))
+* Lua
* GNU Make
* mal itself
+* MATLAB
+* [miniMAL](https://github.com/kanaka/miniMAL)
+* OCaml
* Perl
* PHP
* Postscript
* Python
+* R
+* Racket
* Ruby
+* Rust
+* Scala
+* Visual Basic.NET
-Mal is also a learning tool. Each implentation of mal is separated
+Mal is a learning tool. See the ([make-a-lisp process
+guide](process/guide.md)). Each implementation of mal is separated
into 11 incremental, self-contained (and testable) steps that
demonstrate core concepts of Lisp. The last step is capable of
self-hosting (running the mal implemenation of mal).
The mal (make a lisp) steps are:
-* step0_repl
-* step1_read_print
-* step2_eval
-* step3_env
-* step4_if_fn_do
-* step5_tco
-* step6_file
-* step7_quote
-* step8_macros
-* step9_interop
-* stepA_more
+* [step0_repl](process/guide.md#step0)
+* [step1_read_print](process/guide.md#step1)
+* [step2_eval](process/guide.md#step2)
+* [step3_env](process/guide.md#step3)
+* [step4_if_fn_do](process/guide.md#step4)
+* [step5_tco](process/guide.md#step5)
+* [step6_file](process/guide.md#step6)
+* [step7_quote](process/guide.md#step7)
+* [step8_macros](process/guide.md#step8)
+* [step9_try](process/guide.md#step9)
+* [stepA_mal](process/guide.md#stepA)
Mal was presented publicly for the first time in a lightning talk at
@@ -56,8 +71,8 @@ bash stepX_YYY.sh
### C
-The C implementation of mal requires the following libraries: glib,
-libffi6 and either the libedit or GNU readline library.
+The C implementation of mal requires the following libraries (lib and
+header packages): glib, libffi6 and either the libedit or GNU readline library.
```
cd c
@@ -74,7 +89,7 @@ required to build and run the C# implementation.
```
cd cs
make
-mono ./stepX_YYY
+mono ./stepX_YYY.exe
```
@@ -85,8 +100,51 @@ cd clojure
lein with-profile +stepX trampoline run
```
+### CoffeeScript
+
+```
+sudo npm install -g coffee-script
+cd coffee
+coffee ./stepX_YYY
+```
+
+### Forth
+
+```
+cd forth
+gforth stepX_YYY.fs
+```
+
+### Go
+
+You Go implementation of mal requires that go is installed on on the
+path. The implementation has been tested with Go 1.3.1.
+
+```
+cd go
+make
+./stepX_YYY
+```
+
+
+### Haskell
+
+Install the Haskell compiler (ghc/ghci), the Haskell platform and
+either the editline package (BSD) or the readline package (GPL). On
+Ubuntu these packages are: ghc, haskell-platform,
+libghc-readline-dev/libghc-editline-dev
+
+```
+cd haskell
+make
+./stepX_YYY
+```
+
+
### Java 1.7
+The Java implementation of mal requires maven2 to build.
+
```
cd java
mvn compile
@@ -103,6 +161,17 @@ npm update
node stepX_YYY.js
```
+### Lua
+
+Running the Lua implementation of mal requires lua 5.1 or later,
+luarocks and the lua-rex-pcre library installed.
+
+```
+cd lua
+make # to build and link linenoise.so
+./stepX_YYY.lua
+```
+
### Mal
Running the mal implementation of mal involves running stepA of one of
@@ -122,6 +191,44 @@ cd make
make -f stepX_YYY.mk
```
+### OCaml 4.01.0
+
+```
+cd ocaml
+make
+./stepX_YYY
+```
+
+### MATLAB
+
+The MATLAB implementation of mal has been tested with MATLAB version
+R2014a on Linux. Note that MATLAB is a commercial product. It should
+be fairly simple to support GNU Octave once it support classdef object
+syntax.
+
+```
+cd matlab
+./stepX_YYY
+matlab -nodisplay -nosplash -nodesktop -nojvm -r "stepX_YYY();quit;"
+ # OR with command line arguments
+matlab -nodisplay -nosplash -nodesktop -nojvm -r "stepX_YYY('arg1','arg2');quit;"
+```
+
+### miniMAL
+
+[miniMAL](https://github.com/kanaka/miniMAL) is small Lisp interpreter
+implemented in less than 1024 bytes of JavaScript. To run the miniMAL
+implementation of mal you need to download/install the miniMAL
+interpreter (which requires Node.js).
+```
+cd miniMAL
+# Download miniMAL and dependencies
+npm install
+export PATH=`pwd`/node_modules/minimal-lisp/:$PATH
+# Now run mal implementation in miniMAL
+miniMAL ./stepX_YYY
+```
+
### Perl 5.8
For readline line editing support, install Term::ReadLine::Perl or
@@ -135,6 +242,9 @@ perl stepX_YYY.pl
### PHP 5.3
+The PHP implementation of mal requires the php command line interface
+to run.
+
```
cd php
php stepX_YYY.php
@@ -142,9 +252,12 @@ php stepX_YYY.php
### Postscript Level 2/3
+The Postscript implementation of mal requires ghostscript to run. It
+has been tested with ghostscript 9.10.
+
```
cd ps
-gs -q -dNODISPLAY stepX_YYY.ps
+gs -q -dNODISPLAY -I./ stepX_YYY.ps
```
### Python (2 or 3)
@@ -154,21 +267,82 @@ cd python
python stepX_YYY.py
```
-### Ruby (1.8)
+### R
+
+The R implementation of mal requires R (r-base-core) to run.
+
+```
+cd r
+make libs # to download and build rdyncall
+Rscript stepX_YYY.r
+```
+
+### Racket (5.3)
+
+The Racket implementation of mal requires the Racket
+compiler/interpreter to run.
+
+```
+cd racket
+./stepX_YYY.rb
+```
+
+### Ruby (1.9+)
```
cd ruby
ruby stepX_YYY.rb
```
+### Rust (0.13)
+
+The rust implementation of mal requires the rust compiler and build
+tool (cargo) to build.
+
+```
+cd rust
+# Need patched pcre lib (should be temporary)
+git clone https://github.com/kanaka/rust-pcre cadencemarseille-pcre
+cargo build
+./target/stepX_YYY
+```
+
+### Scala ###
+
+Install scala and sbt (http://www.scala-sbt.org/0.13/tutorial/Installing-sbt-on-Linux.html):
+
+```
+cd scala
+sbt 'run-main stepX_YYY'
+ # OR
+sbt compile
+scala -classpath target/scala*/classes stepX_YYY
+```
+
+### Visual Basic.NET ###
+
+The VB.NET implementation of mal has been tested on Linux using the Mono
+VB compiler (vbnc) and the Mono runtime (version 2.10.8.1). Both are
+required to build and run the VB.NET implementation.
+
+```
+cd vb
+make
+mono ./stepX_YYY.exe
+```
+
+
+
## Running tests
-The are nearly 400 generic Mal 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.
+### Functional tests
+
+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.
To simplify the process of running tests, a top level Makefile is
provided with convenient test targets.
@@ -199,7 +373,7 @@ make test^step2
make test^step7
```
-* To run a specifc step against a single implementation:
+* To run tests for a specifc step against a single implementation:
```
make test^IMPL^stepX
@@ -208,3 +382,59 @@ make test^IMPL^stepX
make test^ruby^step3
make test^ps^step4
```
+
+### Self-hosted functional tests
+
+* To run the functional tests in self-hosted mode, you specify `mal`
+ as the test implementation and use the `MAL_IMPL` make variable
+ to change the underlying host language (default is JavaScript):
+```
+make MAL_IMPL=IMPL test^mal^step2
+
+# e.g.
+make test^mal^step2 # js is default
+make MAL_IMPL=ruby test^mal^step2
+make MAL_IMPL=python test^mal^step2
+```
+
+
+### Performance tests
+
+* To run performance tests against a single implementation:
+```
+make perf^IMPL
+
+# e.g.
+make perf^js
+```
+
+* To run performance tests against all implementations:
+```
+make perf
+```
+
+### Generating language statistics
+
+* To report line and byte stastics for a single implementation:
+```
+make stats^IMPL
+
+# e.g.
+make stats^js
+```
+
+* To report line and bytes stastics for general Lisp code (env, core
+ and stepA):
+```
+make stats-lisp^IMPL
+
+# e.g.
+make stats-lisp^js
+```
+
+
+## License
+
+Mal (make-a-lisp) is licensed under the MPL 2.0 (Mozilla Public
+License 2.0). See LICENSE.txt for more details.
+
diff --git a/bash/Makefile b/bash/Makefile
index e171f69..dc1d1ad 100644
--- a/bash/Makefile
+++ b/bash/Makefile
@@ -1,12 +1,12 @@
SOURCES_BASE = types.sh reader.sh printer.sh
-SOURCES_LISP = env.sh core.sh stepA_more.sh
+SOURCES_LISP = env.sh core.sh stepA_mal.sh
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
all: mal.sh
mal.sh: $(SOURCES)
cat $+ > $@
- echo "#!/bin/bash" > $@
+ echo "#!/usr/bin/env bash" > $@
cat $+ | grep -v "^source " >> $@
chmod +x $@
diff --git a/bash/core.sh b/bash/core.sh
index c8a0261..ca53c43 100644
--- a/bash/core.sh
+++ b/bash/core.sh
@@ -41,6 +41,11 @@ false? () { _false? "${1}" && r="${__true}" || r="${__false}"; }
symbol? () { _symbol? "${1}" && r="${__true}" || r="${__false}"; }
+# Keyword functions
+
+keyword? () { _keyword? "${1}" && r="${__true}" || r="${__false}"; }
+
+
# Number functions
number? () { _number? "${1}" && r="${__true}" || r="${__false}"; }
@@ -230,6 +235,10 @@ concat () {
nth () {
_nth "${1}" "${ANON["${2}"]}"
+ if [ -z "${r}" ]; then
+ _error "nth: index out of bounds"
+ return
+ fi
}
empty? () { _empty? "${1}" && r="${__true}" || r="${__false}"; }
@@ -316,7 +325,10 @@ declare -A core_ns=(
[nil?]=nil?
[true?]=true?
[false?]=false?
+ [symbol]=_symbol
[symbol?]=symbol?
+ [keyword]=_keyword
+ [keyword?]=keyword?
[pr-str]=pr_str
[str]=str
diff --git a/bash/env.sh b/bash/env.sh
index 2eabe8b..9595aa2 100644
--- a/bash/env.sh
+++ b/bash/env.sh
@@ -44,7 +44,7 @@ ENV () {
# Find the environment with the key set and return the environment
ENV_FIND () {
- if _contains? "${1}" "${2}"; then
+ if _contains? "${1}" "${ANON["${2}"]}"; then
r="${1}"
else
local obj="${ANON["${1}"]}"
@@ -63,16 +63,18 @@ ENV_FIND () {
ENV_GET () {
ENV_FIND "${1}" "${2}"
local env="${r}"
+ local key="${ANON["${2}"]}"
if [[ "${r}" ]]; then
local obj="${ANON["${env}"]}"
- eval r="\${${obj}["${2}"]}"
+ eval r="\${${obj}["${key}"]}"
else
- _error "'${2}' not found"
+ _error "'${key}' not found"
fi
}
ENV_SET () {
- _assoc! "${1}" "${2}" "${3}"
+ local key="${ANON["${2}"]}"
+ _assoc! "${1}" "${key}" "${3}"
}
fi
diff --git a/bash/printer.sh b/bash/printer.sh
index 911db17..0d23028 100644
--- a/bash/printer.sh
+++ b/bash/printer.sh
@@ -29,28 +29,42 @@ symbol_pr_str () {
r="${r//__STAR__/*}"
}
-string_pr_str () {
+keyword_pr_str () {
+ string_pr_str "${1}"
+}
+
+_raw_string_pr_str () {
+ local s="${1}"
local print_readably="${2}"
- if [ "${print_readably}" == "yes" ]; then
- local s="${ANON["${1}"]}"
+ if [[ "${s:0:1}" = "${__keyw}" ]]; then
+ r=":${s:1}"
+ elif [ "${print_readably}" == "yes" ]; then
s="${s//\\/\\\\}"
r="\"${s//\"/\\\"}\""
else
- r="${ANON["${1}"]}"
+ r="${s}"
fi
r="${r//__STAR__/$'*'}"
}
+string_pr_str () {
+ _raw_string_pr_str "${ANON["${1}"]}" "${2}"
+}
+
function_pr_str () { r="${ANON["${1}"]}"; }
+bash_pr_str () {
+ r="$(declare -f -p ${1})"
+}
+
hash_map_pr_str () {
local print_readably="${2}"
local res=""; local val=""
local hm="${ANON["${1}"]}"
eval local keys="\${!${hm}[@]}"
for key in ${keys}; do
- #res="${res} \"${ANON["${key}"]}\""
- res="${res} \"${key//__STAR__/$'*'}\""
+ _raw_string_pr_str "${key}" "${print_readably}"
+ res="${res} ${r}"
eval val="\${${hm}[\"${key}\"]}"
_pr_str "${val}" "${print_readably}"
res="${res} ${r}"
diff --git a/bash/reader.sh b/bash/reader.sh
index 585b152..a00e7a1 100644
--- a/bash/reader.sh
+++ b/bash/reader.sh
@@ -15,6 +15,7 @@ READ_ATOM () {
\"*) token="${token:1:-1}"
token="${token//\\\"/\"}"
_string "${token}" ;;
+ :*) _keyword "${token:1}" ;;
nil) r="${__nil}" ;;
true) r="${__true}" ;;
false) r="${__false}" ;;
@@ -113,7 +114,7 @@ TOKENIZE () {
chunk=$(( chunk + ${chunksz} ))
fi
(( ${#str} == 0 )) && break
- [[ "${str}" =~ ^^([][{}\(\)^@])|^(~@)|(\"(\\.|[^\\\"])*\")|^(;[^$'\n']*)|^([~\'\`])|^([^][ ~\`\'\";{}\(\)^@]+)|^[,]|^[[:space:]]+ ]]
+ [[ "${str}" =~ ^^([][{}\(\)^@])|^(~@)|(\"(\\.|[^\\\"])*\")|^(;[^$'\n']*)|^([~\'\`])|^([^][ ~\`\'\";{}\(\)^@\,]+)|^[,]|^[[:space:]]+ ]]
match=${BASH_REMATCH[0]}
str="${str:${#match}}"
token="${match//$'\n'/}"
@@ -123,9 +124,8 @@ TOKENIZE () {
idx=$(( idx + 1 ))
fi
if [ -z "${match}" ]; then
- echo >&2 "Tokenizing error at: ${str:0:50}"
_error "Tokenizing error at: ${str:0:50}"
- break
+ return 1
fi
done
}
@@ -134,9 +134,9 @@ TOKENIZE () {
# read in r.
READ_STR () {
declare -a __reader_tokens
- TOKENIZE "${*}" # sets __reader_tokens
+ TOKENIZE "${*}" || return 1 # sets __reader_tokens
#set | grep ^__reader_tokens
- if [ -z "${__reader_tokens[k]}" ]; then
+ if [ -z "${__reader_tokens[0]}" ]; then
r=
return 1 # No tokens
fi
diff --git a/bash/step0_repl.sh b/bash/step0_repl.sh
index 261ecc2..8e1fff0 100755
--- a/bash/step0_repl.sh
+++ b/bash/step0_repl.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
READ () {
read -u 0 -e -p "user> " r
diff --git a/bash/step1_read_print.sh b/bash/step1_read_print.sh
index e7ca283..881c0c3 100755
--- a/bash/step1_read_print.sh
+++ b/bash/step1_read_print.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -31,7 +31,7 @@ PRINT () {
# repl
REP () {
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}"
PRINT "${r}"
}
diff --git a/bash/step2_eval.sh b/bash/step2_eval.sh
index 224f181..54c645b 100755
--- a/bash/step2_eval.sh
+++ b/bash/step2_eval.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -74,7 +74,7 @@ PRINT () {
declare -A REPL_ENV
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" REPL_ENV
PRINT "${r}"
}
diff --git a/bash/step3_env.sh b/bash/step3_env.sh
index c81805a..f00a6cd 100755
--- a/bash/step3_env.sh
+++ b/bash/step3_env.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -17,8 +17,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -55,10 +54,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -66,7 +64,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
EVAL "${a2}" "${let_env}"
@@ -97,7 +95,7 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
@@ -107,10 +105,10 @@ minus () { r=$(( ${ANON["${1}"]} - ${ANON["${2}"]} )); _number "${r}"; }
multiply () { r=$(( ${ANON["${1}"]} * ${ANON["${2}"]} )); _number "${r}"; }
divide () { r=$(( ${ANON["${1}"]} / ${ANON["${2}"]} )); _number "${r}"; }
-ENV_SET "${REPL_ENV}" "+" plus
-ENV_SET "${REPL_ENV}" "-" minus
-ENV_SET "${REPL_ENV}" "__STAR__" multiply
-ENV_SET "${REPL_ENV}" "/" divide
+_symbol "+"; ENV_SET "${REPL_ENV}" "${r}" plus
+_symbol "-"; ENV_SET "${REPL_ENV}" "${r}" minus
+_symbol "__STAR__"; ENV_SET "${REPL_ENV}" "${r}" multiply
+_symbol "/"; ENV_SET "${REPL_ENV}" "${r}" divide
# repl loop
while true; do
diff --git a/bash/step4_if_fn_do.sh b/bash/step4_if_fn_do.sh
index 07bf3bf..fdd8ded 100755
--- a/bash/step4_if_fn_do.sh
+++ b/bash/step4_if_fn_do.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -18,8 +18,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -56,10 +55,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -67,7 +65,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
EVAL "${a2}" "${let_env}"
@@ -78,6 +76,7 @@ EVAL () {
_last "${r}"
return ;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -120,13 +119,17 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
# core.mal: defined using the language itself
diff --git a/bash/step5_tco.sh b/bash/step5_tco.sh
index ea5b72d..cee2763 100755
--- a/bash/step5_tco.sh
+++ b/bash/step5_tco.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -18,8 +18,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -57,10 +56,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -68,7 +66,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
ast="${a2}"
@@ -84,6 +82,7 @@ EVAL () {
# Continue loop
;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -139,13 +138,17 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
# core.mal: defined using the language itself
diff --git a/bash/step6_file.sh b/bash/step6_file.sh
index 168698a..e8c3bce 100755
--- a/bash/step6_file.sh
+++ b/bash/step6_file.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -18,8 +18,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -57,10 +56,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -68,7 +66,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
ast="${a2}"
@@ -84,6 +82,7 @@ EVAL () {
# Continue loop
;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -139,19 +138,24 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
_eval () { EVAL "${1}" "${REPL_ENV}"; }
_fref "eval" _eval
_list; argv="${r}"
for _arg in "${@:2}"; do _string "${_arg}"; _conj! "${argv}" "${r}"; done
-ENV_SET "${REPL_ENV}" "__STAR__ARGV__STAR__" "${argv}";
+_symbol "__STAR__ARGV__STAR__"
+ENV_SET "${REPL_ENV}" "${r}" "${argv}";
# core.mal: defined using the language itself
REP "(def! not (fn* (a) (if a false true)))"
diff --git a/bash/step7_quote.sh b/bash/step7_quote.sh
index 8319f64..1952c99 100755
--- a/bash/step7_quote.sh
+++ b/bash/step7_quote.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -56,8 +56,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -84,8 +83,7 @@ EVAL () {
r=
[[ "${__ERROR}" ]] && return 1
#_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
- _obj_type "${ast}"; local ot="${r}"
- if [[ "${ot}" != "list" ]]; then
+ if ! _list? "${ast}"; then
EVAL_AST "${ast}" "${env}"
return
fi
@@ -95,10 +93,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -106,7 +103,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
ast="${a2}"
@@ -130,6 +127,7 @@ EVAL () {
# Continue loop
;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -185,19 +183,24 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
_eval () { EVAL "${1}" "${REPL_ENV}"; }
_fref "eval" _eval
_list; argv="${r}"
for _arg in "${@:2}"; do _string "${_arg}"; _conj! "${argv}" "${r}"; done
-ENV_SET "${REPL_ENV}" "__STAR__ARGV__STAR__" "${argv}";
+_symbol "__STAR__ARGV__STAR__"
+ENV_SET "${REPL_ENV}" "${r}" "${argv}";
# core.mal: defined using the language itself
REP "(def! not (fn* (a) (if a false true)))"
diff --git a/bash/step8_macros.sh b/bash/step8_macros.sh
index b21c25f..0503ca9 100755
--- a/bash/step8_macros.sh
+++ b/bash/step8_macros.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -54,9 +54,11 @@ IS_MACRO_CALL () {
if ! _list? "${1}"; then return 1; fi
_nth "${1}" 0; local a0="${r}"
if _symbol? "${a0}"; then
- ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_"
+ ENV_FIND "${2}" "${a0}"
if [[ "${r}" ]]; then
- return 0
+ ENV_GET "${2}" "${a0}"
+ [ "${ANON["${r}_ismacro_"]}" ]
+ return $?
fi
fi
return 1
@@ -66,7 +68,7 @@ MACROEXPAND () {
local ast="${1}" env="${2}"
while IS_MACRO_CALL "${ast}" "${env}"; do
_nth "${ast}" 0; local a0="${r}"
- ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}"
+ ENV_GET "${env}" "${a0}"; local mac="${ANON["${r}"]}"
_rest "${ast}"
${mac%%@*} ${ANON["${r}"]}
ast="${r}"
@@ -81,8 +83,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -122,10 +123,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -133,7 +133,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
ast="${a2}"
@@ -149,10 +149,10 @@ EVAL () {
# Continue loop
;;
defmacro!)
- local k="${ANON["${a1}"]}"
EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
- ENV_SET "${env}" "${k}_ismacro_" "yes"
+ [[ "${__ERROR}" ]] && return 1
+ ANON["${r}_ismacro_"]="yes"
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
macroexpand)
MACROEXPAND "${a1}" "${env}"
@@ -166,6 +166,7 @@ EVAL () {
# Continue loop
;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -221,19 +222,24 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
_eval () { EVAL "${1}" "${REPL_ENV}"; }
_fref "eval" _eval
_list; argv="${r}"
for _arg in "${@:2}"; do _string "${_arg}"; _conj! "${argv}" "${r}"; done
-ENV_SET "${REPL_ENV}" "__STAR__ARGV__STAR__" "${argv}";
+_symbol "__STAR__ARGV__STAR__"
+ENV_SET "${REPL_ENV}" "${r}" "${argv}";
# core.mal: defined using the language itself
REP "(def! not (fn* (a) (if a false true)))"
diff --git a/bash/step9_interop.sh b/bash/step9_try.sh
index 1e00ede..775e0dd 100755
--- a/bash/step9_interop.sh
+++ b/bash/step9_try.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -54,9 +54,11 @@ IS_MACRO_CALL () {
if ! _list? "${1}"; then return 1; fi
_nth "${1}" 0; local a0="${r}"
if _symbol? "${a0}"; then
- ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_"
+ ENV_FIND "${2}" "${a0}"
if [[ "${r}" ]]; then
- return 0
+ ENV_GET "${2}" "${a0}"
+ [ "${ANON["${r}_ismacro_"]}" ]
+ return $?
fi
fi
return 1
@@ -66,7 +68,7 @@ MACROEXPAND () {
local ast="${1}" env="${2}"
while IS_MACRO_CALL "${ast}" "${env}"; do
_nth "${ast}" 0; local a0="${r}"
- ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}"
+ ENV_GET "${env}" "${a0}"; local mac="${ANON["${r}"]}"
_rest "${ast}"
${mac%%@*} ${ANON["${r}"]}
ast="${r}"
@@ -81,8 +83,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -122,10 +123,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -133,7 +133,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
ast="${a2}"
@@ -149,22 +149,26 @@ EVAL () {
# Continue loop
;;
defmacro!)
- local k="${ANON["${a1}"]}"
EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
- ENV_SET "${env}" "${k}_ismacro_" "yes"
+ [[ "${__ERROR}" ]] && return 1
+ ANON["${r}_ismacro_"]="yes"
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
macroexpand)
MACROEXPAND "${a1}" "${env}"
return ;;
- sh*) MACROEXPAND "${a1}" "${env}"
- EVAL "${r}" "${env}"
- local output=""
- local line=""
- while read line; do
- output="${output}${line}\n"
- done < <(eval ${ANON["${r}"]})
- _string "${output%\\n}"
+ try*) EVAL "${a1}" "${env}"
+ [[ -z "${__ERROR}" ]] && return
+ _nth "${a2}" 0; local a20="${r}"
+ if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then
+ _nth "${a2}" 1; local a21="${r}"
+ _nth "${a2}" 2; local a22="${r}"
+ _list "${a21}"; local binds="${r}"
+ ENV "${env}" "${binds}" "${__ERROR}"
+ local try_env="${r}"
+ __ERROR=
+ EVAL "${a22}" "${try_env}"
+ fi # if no catch* clause, just propagate __ERROR
return ;;
do) _count "${ast}"
_slice "${ast}" 1 $(( ${r} - 2 ))
@@ -175,6 +179,7 @@ EVAL () {
# Continue loop
;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -230,19 +235,24 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
_eval () { EVAL "${1}" "${REPL_ENV}"; }
_fref "eval" _eval
_list; argv="${r}"
for _arg in "${@:2}"; do _string "${_arg}"; _conj! "${argv}" "${r}"; done
-ENV_SET "${REPL_ENV}" "__STAR__ARGV__STAR__" "${argv}";
+_symbol "__STAR__ARGV__STAR__"
+ENV_SET "${REPL_ENV}" "${r}" "${argv}";
# core.mal: defined using the language itself
REP "(def! not (fn* (a) (if a false true)))"
diff --git a/bash/stepA_more.sh b/bash/stepA_mal.sh
index 6c3d6c9..7b43496 100755
--- a/bash/stepA_more.sh
+++ b/bash/stepA_mal.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
source $(dirname $0)/reader.sh
source $(dirname $0)/printer.sh
@@ -54,9 +54,11 @@ IS_MACRO_CALL () {
if ! _list? "${1}"; then return 1; fi
_nth "${1}" 0; local a0="${r}"
if _symbol? "${a0}"; then
- ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_"
+ ENV_FIND "${2}" "${a0}"
if [[ "${r}" ]]; then
- return 0
+ ENV_GET "${2}" "${a0}"
+ [ "${ANON["${r}_ismacro_"]}" ]
+ return $?
fi
fi
return 1
@@ -66,7 +68,7 @@ MACROEXPAND () {
local ast="${1}" env="${2}"
while IS_MACRO_CALL "${ast}" "${env}"; do
_nth "${ast}" 0; local a0="${r}"
- ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}"
+ ENV_GET "${env}" "${a0}"; local mac="${ANON["${r}"]}"
_rest "${ast}"
${mac%%@*} ${ANON["${r}"]}
ast="${r}"
@@ -81,8 +83,7 @@ EVAL_AST () {
_obj_type "${ast}"; local ot="${r}"
case "${ot}" in
symbol)
- local val="${ANON["${ast}"]}"
- ENV_GET "${env}" "${val}"
+ ENV_GET "${env}" "${ast}"
return ;;
list)
_map_with_type _list EVAL "${ast}" "${env}" ;;
@@ -122,10 +123,9 @@ EVAL () {
_nth "${ast}" 1; local a1="${r}"
_nth "${ast}" 2; local a2="${r}"
case "${ANON["${a0}"]}" in
- def!) local k="${ANON["${a1}"]}"
- #echo "def! ${k} to ${a2} in ${env}"
- EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
+ def!) EVAL "${a2}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
let*) ENV "${env}"; local let_env="${r}"
local let_pairs=(${ANON["${a1}"]})
@@ -133,7 +133,7 @@ EVAL () {
#echo "let: [${let_pairs[*]}] for ${a2}"
while [[ "${let_pairs["${idx}"]}" ]]; do
EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
- ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}"
idx=$(( idx + 2))
done
ast="${a2}"
@@ -149,16 +149,15 @@ EVAL () {
# Continue loop
;;
defmacro!)
- local k="${ANON["${a1}"]}"
EVAL "${a2}" "${env}"
- ENV_SET "${env}" "${k}" "${r}"
- ENV_SET "${env}" "${k}_ismacro_" "yes"
+ [[ "${__ERROR}" ]] && return 1
+ ANON["${r}_ismacro_"]="yes"
+ ENV_SET "${env}" "${a1}" "${r}"
return ;;
macroexpand)
MACROEXPAND "${a1}" "${env}"
return ;;
- sh*) MACROEXPAND "${a1}" "${env}"
- EVAL "${r}" "${env}"
+ sh*) EVAL "${a1}" "${env}"
local output=""
local line=""
while read line; do
@@ -166,8 +165,7 @@ EVAL () {
done < <(eval ${ANON["${r}"]})
_string "${output%\\n}"
return ;;
- try*) MACROEXPAND "${a1}" "${env}"
- EVAL "${r}" "${env}"
+ try*) EVAL "${a1}" "${env}"
[[ -z "${__ERROR}" ]] && return
_nth "${a2}" 0; local a20="${r}"
if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then
@@ -177,8 +175,7 @@ EVAL () {
ENV "${env}" "${binds}" "${__ERROR}"
local try_env="${r}"
__ERROR=
- MACROEXPAND "${a22}" "${try_env}"
- EVAL "${r}" "${try_env}"
+ EVAL "${a22}" "${try_env}"
fi # if no catch* clause, just propagate __ERROR
return ;;
do) _count "${ast}"
@@ -190,6 +187,7 @@ EVAL () {
# Continue loop
;;
if) EVAL "${a1}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
# eval false form
_nth "${ast}" 3; local a3="${r}"
@@ -245,19 +243,24 @@ PRINT () {
ENV; REPL_ENV="${r}"
REP () {
r=
- READ "${1}" || return 1
+ READ "${1}"
EVAL "${r}" "${REPL_ENV}"
PRINT "${r}"
}
# core.sh: defined using bash
-_fref () { _function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+_fref () {
+ _symbol "${1}"; local sym="${r}"
+ _function "${2} \"\${@}\""
+ ENV_SET "${REPL_ENV}" "${sym}" "${r}"
+}
for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done
_eval () { EVAL "${1}" "${REPL_ENV}"; }
_fref "eval" _eval
_list; argv="${r}"
for _arg in "${@:2}"; do _string "${_arg}"; _conj! "${argv}" "${r}"; done
-ENV_SET "${REPL_ENV}" "__STAR__ARGV__STAR__" "${argv}";
+_symbol "__STAR__ARGV__STAR__"
+ENV_SET "${REPL_ENV}" "${r}" "${argv}";
# core.mal: defined using the language itself
REP "(def! *host-language* \"bash\")"
diff --git a/bash/tests/step9_interop.mal b/bash/tests/stepA_mal.mal
index bf3eabd..bf3eabd 100644
--- a/bash/tests/step9_interop.mal
+++ b/bash/tests/stepA_mal.mal
diff --git a/bash/types.sh b/bash/types.sh
index 6781492..4c2c824 100644
--- a/bash/types.sh
+++ b/bash/types.sh
@@ -8,6 +8,7 @@ __mal_types_included=true
declare -A ANON
__obj_magic=__5bal7
+__keyw=$(echo -en "\u029e")
__obj_hash_code=${__obj_hash_code:-0}
__new_obj_hash_code () {
@@ -50,7 +51,9 @@ _obj_type () {
list) r="list" ;;
numb) r="number" ;;
func) r="function" ;;
- strn) r="string" ;;
+ strn)
+ local s="${ANON["${1}"]}"
+ [[ "${s:0:1}" = "${__keyw}" ]] && r="keyword" || r="string" ;;
_nil) r="nil" ;;
true) r="true" ;;
fals) r="false" ;;
@@ -71,7 +74,7 @@ _equal? () {
fi
fi
case "${ot1}" in
- string|symbol|number)
+ string|symbol|keyword|number)
[[ "${ANON["${1}"]}" == "${ANON["${2}"]}" ]] ;;
list|vector|hash_map)
_count "${1}"; local sz1="${r}"
@@ -104,11 +107,27 @@ _false? () { [[ ${1} =~ ^fals_ ]]; }
_symbol () {
__new_obj_hash_code
r="symb_${r}"
- ANON["${r}"]="${1//$'\*'/__STAR__}"
+ ANON["${r}"]="${1//\*/__STAR__}"
}
_symbol? () { [[ ${1} =~ ^symb_ ]]; }
+# Keywords
+
+_keyword () {
+ local k="${1}"
+ __new_obj_hash_code
+ r="strn_${r}"
+ [[ "${1:1:1}" = "${__keyw}" ]] || k="${__keyw}${1}"
+ ANON["${r}"]="${k//\*/__STAR__}"
+}
+_keyword? () {
+ [[ ${1} =~ ^strn_ ]] || return 1
+ local s="${ANON["${1}"]}"
+ [[ "${s:0:1}" = "${__keyw}" ]]
+}
+
+
# Numbers
_number () {
@@ -124,7 +143,7 @@ _number? () { [[ ${1} =~ ^numb_ ]]; }
_string () {
__new_obj_hash_code
r="strn_${r}"
- ANON["${r}"]="${1//$'\*'/__STAR__}"
+ ANON["${r}"]="${1//\*/__STAR__}"
}
_string? () { [[ ${1} =~ ^strn_ ]]; }
@@ -173,7 +192,7 @@ _hash_map () {
__new_obj_hash_code
local name="hmap_${r}"
local obj="${__obj_magic}_${name}"
- declare -A -g ${obj}
+ declare -A -g ${obj}; eval "${obj}=()"
ANON["${name}"]="${obj}"
while [[ "${1}" ]]; do
@@ -245,7 +264,7 @@ _sequential? () {
_nth () {
local temp=(${ANON["${1}"]})
- r=${temp[${2}]}
+ r="${temp[${2}]}"
}
_first () {
@@ -285,8 +304,12 @@ _conj! () {
_count () {
- local temp=(${ANON["${1}"]})
- r=${#temp[*]}
+ if _nil? "${1}"; then
+ r="0"
+ else
+ local temp=(${ANON["${1}"]})
+ r=${#temp[*]}
+ fi
}
# Slice a sequence object $1 starting at $2 of length $3
diff --git a/c/Makefile b/c/Makefile
index b8d3130..e51e04b 100644
--- a/c/Makefile
+++ b/c/Makefile
@@ -9,7 +9,7 @@ TESTS =
SOURCES_BASE = readline.h readline.c types.h types.c \
reader.h reader.c printer.h printer.c \
interop.h interop.c
-SOURCES_LISP = env.c core.h core.c stepA_more.c
+SOURCES_LISP = env.c core.h core.c stepA_mal.c
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
@@ -17,7 +17,7 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
SRCS = step0_repl.c step1_read_print.c step2_eval.c step3_env.c \
step4_if_fn_do.c step5_tco.c step6_file.c step7_quote.c \
- step8_macros.c step9_interop.c stepA_more.c
+ step8_macros.c step9_try.c stepA_mal.c
OBJS = $(SRCS:%.c=%.o)
BINS = $(OBJS:%.o=%)
OTHER_OBJS = types.o readline.o reader.o printer.o env.o core.o interop.o
@@ -53,7 +53,7 @@ $(BINS): %: %.o
clean:
rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal
-.PHONY: stats tests $(TESTS)
+.PHONY: stats stats-lisp tests $(TESTS)
stats: $(SOURCES)
@wc $^
diff --git a/c/core.c b/c/core.c
index 10c9fc9..8e420e9 100644
--- a/c/core.c
+++ b/c/core.c
@@ -40,7 +40,23 @@ MalVal *symbol(MalVal *args) {
return args;
}
-MalVal *symbol_Q(MalVal *seq) { return seq->type & MAL_SYMBOL ? &mal_true : &mal_false; }
+MalVal *symbol_Q(MalVal *seq) {
+ return seq->type & MAL_SYMBOL ? &mal_true : &mal_false; }
+
+
+// Keyword functions
+
+MalVal *keyword(MalVal *args) {
+ assert_type(args, MAL_STRING,
+ "keyword called with non-string value");
+ return malval_new_keyword(args->val.string);
+}
+
+MalVal *keyword_Q(MalVal *seq) {
+ return seq->type & MAL_STRING && seq->val.string[0] == '\x7f'
+ ? &mal_true
+ : &mal_false;
+}
// String functions
@@ -431,7 +447,7 @@ MalVal *swap_BANG(MalVal *args) {
-core_ns_entry core_ns[54] = {
+core_ns_entry core_ns[56] = {
{"=", (void*(*)(void*))equal_Q, 2},
{"throw", (void*(*)(void*))throw, 1},
{"nil?", (void*(*)(void*))nil_Q, 1},
@@ -439,6 +455,8 @@ core_ns_entry core_ns[54] = {
{"false?", (void*(*)(void*))false_Q, 1},
{"symbol", (void*(*)(void*))symbol, 1},
{"symbol?", (void*(*)(void*))symbol_Q, 1},
+ {"keyword", (void*(*)(void*))keyword, 1},
+ {"keyword?", (void*(*)(void*))keyword_Q, 1},
{"pr-str", (void*(*)(void*))pr_str, -1},
{"str", (void*(*)(void*))str, -1},
diff --git a/c/core.h b/c/core.h
index 49f8bee..82070ff 100644
--- a/c/core.h
+++ b/c/core.h
@@ -10,6 +10,6 @@ typedef struct {
int arg_cnt;
} core_ns_entry;
-extern core_ns_entry core_ns[54];
+extern core_ns_entry core_ns[56];
#endif
diff --git a/c/env.c b/c/env.c
index d4b8f32..0114d1e 100644
--- a/c/env.c
+++ b/c/env.c
@@ -25,10 +25,10 @@ Env *new_env(Env *outer, MalVal* binds, MalVal *exprs) {
if (i > exprs_len) { break; }
if (_nth(binds, i)->val.string[0] == '&') {
varargs = 1;
- env_set(e, _nth(binds, i+1)->val.string, _slice(exprs, i, _count(exprs)));
+ env_set(e, _nth(binds, i+1), _slice(exprs, i, _count(exprs)));
break;
} else {
- env_set(e, _nth(binds, i)->val.string, _nth(exprs, i));
+ env_set(e, _nth(binds, i), _nth(exprs, i));
}
}
assert(varargs || (binds_len == exprs_len),
@@ -39,8 +39,8 @@ Env *new_env(Env *outer, MalVal* binds, MalVal *exprs) {
return e;
}
-Env *env_find(Env *env, char *key) {
- void *val = g_hash_table_lookup(env->table, key);
+Env *env_find(Env *env, MalVal *key) {
+ void *val = g_hash_table_lookup(env->table, key->val.string);
if (val) {
return env;
} else if (env->outer) {
@@ -50,13 +50,13 @@ Env *env_find(Env *env, char *key) {
}
}
-MalVal *env_get(Env *env, char *key) {
+MalVal *env_get(Env *env, MalVal *key) {
Env *e = env_find(env, key);
- assert(e, "'%s' not found", key);
- return g_hash_table_lookup(e->table, key);
+ assert(e, "'%s' not found", key->val.string);
+ return g_hash_table_lookup(e->table, key->val.string);
}
-Env *env_set(Env *env, char *key, MalVal *val) {
- g_hash_table_insert(env->table, key, val);
+Env *env_set(Env *env, MalVal *key, MalVal *val) {
+ g_hash_table_insert(env->table, key->val.string, val);
return env;
}
diff --git a/c/printer.c b/c/printer.c
index 0669cf6..786d89e 100644
--- a/c/printer.c
+++ b/c/printer.c
@@ -5,7 +5,8 @@
char *_pr_str_hash_map(MalVal *obj, int print_readably) {
int start = 1;
- char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL;
+ char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL,
+ *key2 = NULL;
GHashTableIter iter;
gpointer key, value;
@@ -14,14 +15,20 @@ char *_pr_str_hash_map(MalVal *obj, int print_readably) {
g_hash_table_iter_init (&iter, obj->val.hash_table);
while (g_hash_table_iter_next (&iter, &key, &value)) {
//g_print ("%s/%p ", (const char *) key, (void *) value);
+ if (((char*)key)[0] == '\x7f') {
+ key2 = g_strdup_printf("%s", (char*)key);
+ key2[0] = ':';
+ } else {
+ key2 = g_strdup_printf("\"%s\"", (char*)key);
+ }
repr_tmp1 = _pr_str((MalVal*)value, print_readably);
if (start) {
start = 0;
- repr = g_strdup_printf("{\"%s\" %s", (char *)key, repr_tmp1);
+ repr = g_strdup_printf("{%s %s", (char*)key2, repr_tmp1);
} else {
repr_tmp2 = repr;
- repr = g_strdup_printf("%s \"%s\" %s", repr_tmp2, (char *)key, repr_tmp1);
+ repr = g_strdup_printf("%s %s %s", repr_tmp2, (char*)key2, repr_tmp1);
free(repr_tmp2);
}
free(repr_tmp1);
@@ -70,7 +77,11 @@ char *_pr_str(MalVal *obj, int print_readably) {
repr = g_strdup_printf("false");
break;
case MAL_STRING:
- if (print_readably) {
+ if (obj->val.string[0] == '\x7f') {
+ // Keyword
+ repr = g_strdup_printf("%s", obj->val.string);
+ repr[0] = ':';
+ } else if (print_readably) {
char *repr_tmp = g_strescape(obj->val.string, "");
repr = g_strdup_printf("\"%s\"", repr_tmp);
free(repr_tmp);
@@ -121,7 +132,7 @@ char *_pr_str_args(MalVal *args, char *sep, int print_readably) {
assert_type(args, MAL_LIST|MAL_VECTOR,
"_pr_str called with non-sequential args");
int i;
- char *repr = g_strdup_printf(""),
+ char *repr = g_strdup_printf("%s", ""),
*repr2 = NULL;
for (i=0; i<_count(args); i++) {
MalVal *obj = g_array_index(args->val.array, MalVal*, i);
diff --git a/c/reader.c b/c/reader.c
index dbb7335..ae16321 100644
--- a/c/reader.c
+++ b/c/reader.c
@@ -2,8 +2,9 @@
#include <stdio.h>
#include <string.h>
-#include <glib/gregex.h>
-#include <glib-object.h>
+//#include <glib/gregex.h>
+//#include <glib-object.h>
+#include <glib.h>
#include "types.h"
#include "reader.h"
@@ -121,7 +122,7 @@ MalVal *read_atom(Reader *reader) {
token = reader_next(reader);
//g_print("read_atom token: %s\n", token);
- regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)", 0, 0, &err);
+ regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)", 0, 0, &err);
g_regex_match (regex, token, 0, &matchInfo);
if (g_match_info_fetch_pos(matchInfo, 1, &pos, NULL) && pos != -1) {
@@ -144,8 +145,11 @@ MalVal *read_atom(Reader *reader) {
char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\"", "\"");
atom = malval_new_string(str_tmp);
} else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) {
+ //g_print("read_atom keyword\n");
+ atom = malval_new_keyword(g_match_info_fetch(matchInfo, 7));
+ } else if (g_match_info_fetch_pos(matchInfo, 8, &pos, NULL) && pos != -1) {
//g_print("read_atom symbol\n");
- atom = malval_new_symbol(g_match_info_fetch(matchInfo, 7));
+ atom = malval_new_symbol(g_match_info_fetch(matchInfo, 8));
} else {
malval_free(atom);
atom = NULL;
diff --git a/c/step3_env.c b/c/step3_env.c
index 2f41bc0..cacf9d7 100644
--- a/c/step3_env.c
+++ b/c/step3_env.c
@@ -32,7 +32,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -79,7 +79,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ env_set(env, a1, res);
return res;
} else if (strcmp("let*", a0->val.string) == 0) {
//g_print("eval apply let*\n");
@@ -95,7 +95,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
return EVAL(a2, let_env);
} else {
@@ -143,10 +143,10 @@ void init_repl_env() {
WRAP_INTEGER_OP(multiply,*)
WRAP_INTEGER_OP(divide,/)
- env_set(repl_env, "+", (MalVal *)int_plus);
- env_set(repl_env, "-", (MalVal *)int_minus);
- env_set(repl_env, "*", (MalVal *)int_multiply);
- env_set(repl_env, "/", (MalVal *)int_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);
+ env_set(repl_env, malval_new_symbol("/"), (MalVal *)int_divide);
}
int main()
diff --git a/c/step4_if_fn_do.c b/c/step4_if_fn_do.c
index 84fb760..413bcd6 100644
--- a/c/step4_if_fn_do.c
+++ b/c/step4_if_fn_do.c
@@ -33,7 +33,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -80,7 +80,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -97,7 +98,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
return EVAL(a2, let_env);
} else if ((a0->type & MAL_SYMBOL) &&
@@ -110,12 +111,11 @@ MalVal *EVAL(MalVal *ast, Env *env) {
//g_print("eval apply if\n");
MalVal *a1 = _nth(ast, 1);
MalVal *cond = EVAL(a1, env);
- if (!ast || mal_error) return NULL;
+ if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- MalVal *a3 = _nth(ast, 3);
- if (a3) {
- return EVAL(a3, env);
+ if (ast->val.array->len > 3) {
+ return EVAL(_nth(ast, 3), env);
} else {
return &mal_nil;
}
@@ -179,7 +179,8 @@ void init_repl_env() {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
diff --git a/c/step5_tco.c b/c/step5_tco.c
index edca21b..a1762c8 100644
--- a/c/step5_tco.c
+++ b/c/step5_tco.c
@@ -33,7 +33,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -82,7 +82,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -99,7 +100,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
ast = a2;
env = let_env;
@@ -118,8 +119,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- ast = _nth(ast, 3);
- if (!ast) {
+ if (ast->val.array->len > 3) {
+ ast = _nth(ast, 3);
+ } else {
return &mal_nil;
}
} else {
@@ -190,7 +192,8 @@ void init_repl_env() {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
diff --git a/c/step6_file.c b/c/step6_file.c
index 9ff62a9..409e221 100644
--- a/c/step6_file.c
+++ b/c/step6_file.c
@@ -33,7 +33,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -82,7 +82,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -99,7 +100,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
ast = a2;
env = let_env;
@@ -118,8 +119,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- ast = _nth(ast, 3);
- if (!ast) {
+ if (ast->val.array->len > 3) {
+ ast = _nth(ast, 3);
+ } else {
return &mal_nil;
}
} else {
@@ -190,11 +192,13 @@ void init_repl_env(int argc, char *argv[]) {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ 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, "eval",
+ env_set(repl_env,
+ malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));
MalVal *_argv = _listX(0);
@@ -202,7 +206,7 @@ void init_repl_env(int argc, char *argv[]) {
MalVal *arg = malval_new_string(argv[i]);
g_array_append_val(_argv->val.array, arg);
}
- env_set(repl_env, "*ARGV*", _argv);
+ env_set(repl_env, malval_new_symbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
diff --git a/c/step7_quote.c b/c/step7_quote.c
index d0d1d3d..73250e3 100644
--- a/c/step7_quote.c
+++ b/c/step7_quote.c
@@ -60,7 +60,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -109,7 +109,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -126,7 +127,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
ast = a2;
env = let_env;
@@ -155,8 +156,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- ast = _nth(ast, 3);
- if (!ast) {
+ if (ast->val.array->len > 3) {
+ ast = _nth(ast, 3);
+ } else {
return &mal_nil;
}
} else {
@@ -227,11 +229,13 @@ void init_repl_env(int argc, char *argv[]) {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ 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, "eval",
+ env_set(repl_env,
+ malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));
MalVal *_argv = _listX(0);
@@ -239,7 +243,7 @@ void init_repl_env(int argc, char *argv[]) {
MalVal *arg = malval_new_string(argv[i]);
g_array_append_val(_argv->val.array, arg);
}
- env_set(repl_env, "*ARGV*", _argv);
+ env_set(repl_env, malval_new_symbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
diff --git a/c/step8_macros.c b/c/step8_macros.c
index 3558caf..55c6988 100644
--- a/c/step8_macros.c
+++ b/c/step8_macros.c
@@ -61,15 +61,15 @@ int is_macro_call(MalVal *ast, Env *env) {
if (!ast || ast->type != MAL_LIST) { return 0; }
MalVal *a0 = _nth(ast, 0);
return (a0->type & MAL_SYMBOL) &&
- env_find(env, a0->val.string) &&
- env_get(env, a0->val.string)->ismacro;
+ env_find(env, a0) &&
+ env_get(env, a0)->ismacro;
}
MalVal *macroexpand(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
while (is_macro_call(ast, env)) {
MalVal *a0 = _nth(ast, 0);
- MalVal *mac = env_get(env, a0->val.string);
+ MalVal *mac = env_get(env, a0);
// TODO: this is weird and limits it to 20. FIXME
ast = _apply(mac, _rest(ast));
}
@@ -80,7 +80,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -133,7 +133,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -150,7 +151,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
ast = a2;
env = let_env;
@@ -171,8 +172,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
+ if (mal_error) return NULL;
res->ismacro = TRUE;
- env_set(env, a1->val.string, res);
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("macroexpand", a0->val.string) == 0) {
@@ -193,8 +195,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- ast = _nth(ast, 3);
- if (!ast) {
+ if (ast->val.array->len > 3) {
+ ast = _nth(ast, 3);
+ } else {
return &mal_nil;
}
} else {
@@ -266,11 +269,13 @@ void init_repl_env(int argc, char *argv[]) {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ 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, "eval",
+ env_set(repl_env,
+ malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));
MalVal *_argv = _listX(0);
@@ -278,7 +283,7 @@ void init_repl_env(int argc, char *argv[]) {
MalVal *arg = malval_new_string(argv[i]);
g_array_append_val(_argv->val.array, arg);
}
- env_set(repl_env, "*ARGV*", _argv);
+ env_set(repl_env, malval_new_symbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
diff --git a/c/step9_interop.c b/c/step9_try.c
index 6ba594e..ffba2f9 100644
--- a/c/step9_interop.c
+++ b/c/step9_try.c
@@ -62,15 +62,15 @@ int is_macro_call(MalVal *ast, Env *env) {
if (!ast || ast->type != MAL_LIST) { return 0; }
MalVal *a0 = _nth(ast, 0);
return (a0->type & MAL_SYMBOL) &&
- env_find(env, a0->val.string) &&
- env_get(env, a0->val.string)->ismacro;
+ env_find(env, a0) &&
+ env_get(env, a0)->ismacro;
}
MalVal *macroexpand(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
while (is_macro_call(ast, env)) {
MalVal *a0 = _nth(ast, 0);
- MalVal *mac = env_get(env, a0->val.string);
+ MalVal *mac = env_get(env, a0);
// TODO: this is weird and limits it to 20. FIXME
ast = _apply(mac, _rest(ast));
}
@@ -81,7 +81,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -134,7 +134,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -151,7 +152,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
ast = a2;
env = let_env;
@@ -172,8 +173,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
+ if (mal_error) return NULL;
res->ismacro = TRUE;
- env_set(env, a1->val.string, res);
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("macroexpand", a0->val.string) == 0) {
@@ -181,10 +183,26 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1);
return macroexpand(a1, env);
} else if ((a0->type & MAL_SYMBOL) &&
- strcmp(".", a0->val.string) == 0) {
- //g_print("eval apply .\n");
- MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env);
- return invoke_native(el);
+ strcmp("try*", a0->val.string) == 0) {
+ //g_print("eval apply try*\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a1, env);
+ if (!mal_error) { return res; }
+ MalVal *a20 = _nth(a2, 0);
+ if (strcmp("catch*", a20->val.string) == 0) {
+ MalVal *a21 = _nth(a2, 1);
+ MalVal *a22 = _nth(a2, 2);
+ Env *catch_env = new_env(env,
+ _listX(1, a21),
+ _listX(1, mal_error));
+ //malval_free(mal_error);
+ mal_error = NULL;
+ res = EVAL(a22, catch_env);
+ return res;
+ } else {
+ return &mal_nil;
+ }
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("do", a0->val.string) == 0) {
//g_print("eval apply do\n");
@@ -199,8 +217,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- ast = _nth(ast, 3);
- if (!ast) {
+ if (ast->val.array->len > 3) {
+ ast = _nth(ast, 3);
+ } else {
return &mal_nil;
}
} else {
@@ -272,11 +291,13 @@ void init_repl_env(int argc, char *argv[]) {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ 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, "eval",
+ env_set(repl_env,
+ malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));
MalVal *_argv = _listX(0);
@@ -284,7 +305,7 @@ void init_repl_env(int argc, char *argv[]) {
MalVal *arg = malval_new_string(argv[i]);
g_array_append_val(_argv->val.array, arg);
}
- env_set(repl_env, "*ARGV*", _argv);
+ env_set(repl_env, malval_new_symbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
diff --git a/c/stepA_more.c b/c/stepA_mal.c
index b4b7431..05e9f65 100644
--- a/c/stepA_more.c
+++ b/c/stepA_mal.c
@@ -62,15 +62,15 @@ int is_macro_call(MalVal *ast, Env *env) {
if (!ast || ast->type != MAL_LIST) { return 0; }
MalVal *a0 = _nth(ast, 0);
return (a0->type & MAL_SYMBOL) &&
- env_find(env, a0->val.string) &&
- env_get(env, a0->val.string)->ismacro;
+ env_find(env, a0) &&
+ env_get(env, a0)->ismacro;
}
MalVal *macroexpand(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
while (is_macro_call(ast, env)) {
MalVal *a0 = _nth(ast, 0);
- MalVal *mac = env_get(env, a0->val.string);
+ MalVal *mac = env_get(env, a0);
// TODO: this is weird and limits it to 20. FIXME
ast = _apply(mac, _rest(ast));
}
@@ -81,7 +81,7 @@ MalVal *eval_ast(MalVal *ast, Env *env) {
if (!ast || mal_error) return NULL;
if (ast->type == MAL_SYMBOL) {
//g_print("EVAL symbol: %s\n", ast->val.string);
- return env_get(env, ast->val.string);
+ return env_get(env, ast);
} else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
//g_print("EVAL sequential: %s\n", _pr_str(ast,1));
MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
@@ -134,7 +134,8 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
- env_set(env, a1->val.string, res);
+ if (mal_error) return NULL;
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("let*", a0->val.string) == 0) {
@@ -151,7 +152,7 @@ MalVal *EVAL(MalVal *ast, Env *env) {
key = g_array_index(a1->val.array, MalVal*, i);
val = g_array_index(a1->val.array, MalVal*, i+1);
assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
- env_set(let_env, key->val.string, EVAL(val, let_env));
+ env_set(let_env, key, EVAL(val, let_env));
}
ast = a2;
env = let_env;
@@ -172,8 +173,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
MalVal *a1 = _nth(ast, 1),
*a2 = _nth(ast, 2);
MalVal *res = EVAL(a2, env);
+ if (mal_error) return NULL;
res->ismacro = TRUE;
- env_set(env, a1->val.string, res);
+ env_set(env, a1, res);
return res;
} else if ((a0->type & MAL_SYMBOL) &&
strcmp("macroexpand", a0->val.string) == 0) {
@@ -220,8 +222,9 @@ MalVal *EVAL(MalVal *ast, Env *env) {
if (!cond || mal_error) return NULL;
if (cond->type & (MAL_FALSE|MAL_NIL)) {
// eval false slot form
- ast = _nth(ast, 3);
- if (!ast) {
+ if (ast->val.array->len > 3) {
+ ast = _nth(ast, 3);
+ } else {
return &mal_nil;
}
} else {
@@ -293,11 +296,13 @@ void init_repl_env(int argc, char *argv[]) {
// core.c: defined using C
int i;
for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) {
- env_set(repl_env, core_ns[i].name,
+ env_set(repl_env,
+ 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, "eval",
+ env_set(repl_env,
+ malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));
MalVal *_argv = _listX(0);
@@ -305,7 +310,7 @@ void init_repl_env(int argc, char *argv[]) {
MalVal *arg = malval_new_string(argv[i]);
g_array_append_val(_argv->val.array, arg);
}
- env_set(repl_env, "*ARGV*", _argv);
+ env_set(repl_env, malval_new_symbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
RE(repl_env, "", "(def! *host-language* \"c\")");
diff --git a/c/tests/step9_interop.mal b/c/tests/stepA_mal.mal
index 657e3e7..657e3e7 100644
--- a/c/tests/step9_interop.mal
+++ b/c/tests/stepA_mal.mal
diff --git a/c/types.c b/c/types.c
index 3f1771b..a6c8492 100644
--- a/c/types.c
+++ b/c/types.c
@@ -105,6 +105,12 @@ MalVal *malval_new_symbol(char *val) {
return mv;
}
+MalVal *malval_new_keyword(char *val) {
+ MalVal *mv = malval_new(MAL_STRING, NULL);
+ mv->val.string = g_strdup_printf("\x7f%s", val);
+ return mv;
+}
+
MalVal *malval_new_list(MalType type, GArray *val) {
MalVal *mv = malval_new(type, NULL);
mv->val.array = val;
@@ -422,7 +428,7 @@ MalVal *_nth(MalVal *seq, int idx) {
assert_type(seq, MAL_LIST|MAL_VECTOR,
"_nth called with non-sequential");
if (idx >= _count(seq)) {
- return &mal_nil;
+ abort("nth: index out of range");
}
return g_array_index(seq->val.array, MalVal*, idx);
}
diff --git a/c/types.h b/c/types.h
index aa6c5e3..80a40ac 100644
--- a/c/types.h
+++ b/c/types.h
@@ -15,9 +15,9 @@ typedef struct Env {
} Env;
Env *new_env(Env *outer, struct MalVal* binds, struct MalVal *exprs);
-Env *env_find(Env *env, char *key);
-struct MalVal *env_get(Env *env, char *key);
-Env *env_set(Env *env, char *key, struct MalVal *val);
+Env *env_find(Env *env, struct MalVal *key);
+struct MalVal *env_get(Env *env, struct MalVal *key);
+Env *env_set(Env *env, struct MalVal *key, struct MalVal *val);
// Utility functiosn
@@ -130,6 +130,7 @@ MalVal *malval_new_integer(gint64 val);
MalVal *malval_new_float(gdouble val);
MalVal *malval_new_string(char *val);
MalVal *malval_new_symbol(char *val);
+MalVal *malval_new_keyword(char *val);
MalVal *malval_new_list(MalType type, GArray *val);
MalVal *malval_new_hash_map(GHashTable *val);
MalVal *malval_new_atom(MalVal *val);
diff --git a/clojure/Makefile b/clojure/Makefile
index 2ddfbcc..ec55ac1 100644
--- a/clojure/Makefile
+++ b/clojure/Makefile
@@ -1,5 +1,5 @@
SOURCES_BASE = src/readline.clj src/reader.clj src/printer.clj
-SOURCES_LISP = src/env.clj src/core.clj src/stepA_more.clj
+SOURCES_LISP = src/env.clj src/core.clj src/stepA_mal.clj
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
all:
diff --git a/clojure/project.clj b/clojure/project.clj
index 4e7a15f..447d643 100644
--- a/clojure/project.clj
+++ b/clojure/project.clj
@@ -18,8 +18,8 @@
:step6 {:main step6-file}
:step7 {:main step7-quote}
:step8 {:main step8-macros}
- :step9 {:main step9-interop}
- :stepA {:main stepA-more}}
+ :step9 {:main step9-try}
+ :stepA {:main stepA-mal}}
:main stepA-more)
diff --git a/clojure/src/core.clj b/clojure/src/core.clj
index 6b0d9b5..4438e29 100644
--- a/clojure/src/core.clj
+++ b/clojure/src/core.clj
@@ -5,11 +5,6 @@
(defn mal_throw [obj]
(throw (ex-info "mal exception" {:data obj})))
-;; Number functions
-
-(defn time-ms []
- (System/currentTimeMillis))
-
;; Metadata functions
;; - store metadata at :meta key of the real metadata
(defn mal_with_meta [obj m]
@@ -19,10 +14,6 @@
(defn mal_meta [obj]
(:meta (meta obj)))
-;; Atom functions
-(defn atom? [atm]
- (= (type atm) clojure.lang.Atom))
-
;; core_ns is core namespaces functions
(def core_ns
[['= =]
@@ -30,7 +21,10 @@
['nil? nil?]
['true? true?]
['false? false?]
+ ['symbol symbol]
['symbol? symbol?]
+ ['keyword keyword]
+ ['keyword? keyword?]
['pr-str pr-str]
['str str]
@@ -47,7 +41,7 @@
['- -]
['* *]
['/ /]
- ['time-ms time-ms]
+ ['time-ms (fn time-ms [] (System/currentTimeMillis))]
['list list]
['list? seq?]
@@ -59,8 +53,8 @@
['dissoc dissoc]
['get get]
['contains? contains?]
- ['keys keys]
- ['vals vals]
+ ['keys (fn [hm] (let [ks (keys hm)] (if (nil? ks) '() ks)))]
+ ['vals (fn [hm] (let [vs (vals hm)] (if (nil? vs) '() vs)))]
['sequential? sequential?]
['cons cons]
@@ -77,7 +71,7 @@
['with-meta mal_with_meta]
['meta mal_meta]
['atom atom]
- ['atom? atom?]
+ ['atom? (fn atom? [atm] (= (type atm) clojure.lang.Atom))]
['deref deref]
['reset! reset!]
['swap! swap!]])
diff --git a/clojure/src/readline.clj b/clojure/src/readline.clj
index 0fc449a..a510e13 100644
--- a/clojure/src/readline.clj
+++ b/clojure/src/readline.clj
@@ -27,7 +27,8 @@
(def load-history (jna/to-fn Integer readline/read_history)))
(defn readline [prompt & [lib]]
- (if (not @history-loaded)
+ (when (not @history-loaded)
+ (reset! history-loaded true)
(load-history HISTORY-FILE))
(let [line (readline-call prompt)]
(when line
diff --git a/clojure/src/step9_interop.clj b/clojure/src/step9_try.clj
index c4d67e5..7e18a74 100644
--- a/clojure/src/step9_interop.clj
+++ b/clojure/src/step9_try.clj
@@ -1,4 +1,4 @@
-(ns step9-interop
+(ns step9-try
(:refer-clojure :exclude [macroexpand])
(:require [clojure.repl]
[readline]
@@ -94,9 +94,20 @@
'macroexpand
(macroexpand a1 env)
- 'clj*
- (eval (reader/read-string a1))
-
+ 'try*
+ (if (= 'catch* (nth a2 0))
+ (try
+ (EVAL a1 env)
+ (catch clojure.lang.ExceptionInfo ei
+ (EVAL (nth a2 2) (env/env env
+ [(nth a2 1)]
+ [(:data (ex-data ei))])))
+ (catch Throwable t
+ (EVAL (nth a2 2) (env/env env
+ [(nth a2 1)]
+ [(.getMessage t)]))))
+ (EVAL a1 env))
+
'do
(do (eval-ast (->> ast (drop-last) (drop 1)) env)
(recur (last ast) env))
@@ -161,4 +172,5 @@
(env/env-set repl-env '*ARGV* (rest args))
(if args
(rep (str "(load-file \"" (first args) "\")"))
- (repl-loop)))
+ (do
+ (repl-loop))))
diff --git a/clojure/src/stepA_more.clj b/clojure/src/stepA_mal.clj
index fc7451f..ce289b6 100644
--- a/clojure/src/stepA_more.clj
+++ b/clojure/src/stepA_mal.clj
@@ -1,4 +1,4 @@
-(ns stepA-more
+(ns stepA-mal
(:refer-clojure :exclude [macroexpand])
(:require [clojure.repl]
[readline]
diff --git a/clojure/tests/step9_interop.mal b/clojure/tests/stepA_mal.mal
index b323222..b323222 100644
--- a/clojure/tests/step9_interop.mal
+++ b/clojure/tests/stepA_mal.mal
diff --git a/coffee/Makefile b/coffee/Makefile
new file mode 100644
index 0000000..728d9f2
--- /dev/null
+++ b/coffee/Makefile
@@ -0,0 +1,15 @@
+TESTS =
+
+SOURCES_BASE = node_readline.coffee types.coffee \
+ reader.coffee printer.coffee
+SOURCES_LISP = env.coffee core.coffee stepA_mal.coffee
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#all: mal.rb
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/coffee/core.coffee b/coffee/core.coffee
new file mode 100644
index 0000000..8bb6958
--- /dev/null
+++ b/coffee/core.coffee
@@ -0,0 +1,89 @@
+readline = require "../js/node_readline"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+[_pr_str, println] = [printer._pr_str, printer.println]
+
+# Sequence functions
+conj = (seq, args...) ->
+ switch types._obj_type(seq)
+ when 'list'
+ lst = types._clone(seq)
+ lst.unshift(x) for x in args
+ lst
+ when 'vector'
+ lst = types._clone(seq)
+ lst.push(args...)
+ types._vector(lst...)
+ else throw new Error "conj called on " + types._obj_type(seq)
+
+# Metadata functions
+with_meta = (obj,m) ->
+ new_obj = types._clone(obj)
+ new_obj.__meta__ = m
+ new_obj
+
+
+exports.ns = {
+ '=': (a,b) -> types._equal_Q(a,b),
+ 'throw': (a) -> throw a,
+ 'nil?': types._nil_Q,
+ 'true?': types._true_Q,
+ 'false?': types._false_Q,
+ 'symbol': types._symbol,
+ 'symbol?': types._symbol_Q,
+ 'keyword': types._keyword,
+ 'keyword?': types._keyword_Q,
+
+ 'pr-str': (a...) -> a.map((exp) -> _pr_str(exp,true)).join(" "),
+ 'str': (a...) -> a.map((exp) -> _pr_str(exp,false)).join(""),
+ 'prn': (a...) -> println(a.map((exp) -> _pr_str(exp,true))...),
+ 'println': (a...) -> println(a.map((exp) -> _pr_str(exp,false))...),
+ 'readline': readline.readline,
+ 'read-string': reader.read_str,
+ 'slurp': (a) -> require('fs').readFileSync(a, 'utf-8'),
+ '<': (a,b) -> a<b,
+ '<=': (a,b) -> a<=b,
+ '>': (a,b) -> a>b,
+ '>=': (a,b) -> a>=b,
+ '+': (a,b) -> a+b,
+ '-': (a,b) -> a-b,
+ '*': (a,b) -> a*b,
+ '/': (a,b) -> a/b,
+ 'time-ms': () -> new Date().getTime(),
+
+ 'list': (a...) -> a,
+ 'list?': types._list_Q,
+ 'vector': (a...) -> types._vector(a...),
+ 'vector?': types._vector_Q,
+ 'hash-map': (a...) -> types._hash_map(a...),
+ 'map?': types._hash_map_Q,
+ 'assoc': (a,b...) -> types._assoc_BANG(types._clone(a), b...),
+ 'dissoc': (a,b...) -> types._dissoc_BANG(types._clone(a), b...),
+ 'get': (a,b) -> if a != null and b of a then a[b] else null,
+ 'contains?': (a,b) -> b of a,
+ 'keys': (a) -> k for k of a,
+ 'vals': (a) -> v for k,v of a,
+
+ 'sequential?': types._sequential_Q,
+ 'cons': (a,b) -> [a].concat(b),
+ 'concat': (a=[],b...) -> a.concat(b...),
+ 'nth': (a,b) -> if a.length > b then a[b] else
+ throw new Error "nth: index out of bounds",
+ 'first': (a) -> if a.length > 0 then a[0] else null,
+ 'rest': (a) -> a[1..],
+ 'empty?': (a) -> a.length == 0,
+ 'count': (a) -> if a == null then 0 else a.length,
+ 'apply': (a,b...) -> a(b[0..-2].concat(b[b.length-1])...),
+ 'map': (a,b) -> b.map((x) -> a(x)),
+ 'conj': conj,
+
+ 'with-meta': with_meta,
+ 'meta': (a) -> a.__meta__ or null,
+ 'atom': types._atom,
+ 'atom?': types._atom_Q,
+ 'deref': (a) -> a.val,
+ 'reset!': (a,b) -> a.val = b,
+ 'swap!': (a,b,c...) -> a.val = b([a.val].concat(c)...), }
+
+# vim: ts=2:sw=2
diff --git a/coffee/env.coffee b/coffee/env.coffee
new file mode 100644
index 0000000..80fbf12
--- /dev/null
+++ b/coffee/env.coffee
@@ -0,0 +1,31 @@
+types = require "./types.coffee"
+
+# Env
+exports.Env = class Env
+ constructor: (@outer=null, @binds=[], @exprs=[]) ->
+ @data = {}
+ if @binds.length > 0
+ for b,i in @binds
+ if types._symbol_Q(b) && b.name == "&"
+ @data[@binds[i+1].name] = exprs[i..]
+ break
+ else
+ @data[b.name] = exprs[i]
+ find: (key) ->
+ if not types._symbol_Q(key)
+ throw new Error("env.find key must be symbol")
+ if key.name of @data then @
+ else if @outer then @outer.find(key)
+ else null
+ set: (key, value) ->
+ if not types._symbol_Q(key)
+ throw new Error("env.set key must be symbol")
+ @data[key.name] = value
+ get: (key) ->
+ if not types._symbol_Q(key)
+ throw new Error("env.get key must be symbol")
+ env = @find(key)
+ throw new Error("'" + key.name + "' not found") if !env
+ env.data[key.name]
+
+# vim: ts=2:sw=2
diff --git a/coffee/node_readline.coffee b/coffee/node_readline.coffee
new file mode 100644
index 0000000..31e5ca0
--- /dev/null
+++ b/coffee/node_readline.coffee
@@ -0,0 +1,36 @@
+# IMPORTANT: choose one
+RL_LIB = "libreadline" # NOTE: libreadline is GPL
+#RL_LIB = "libedit"
+
+HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history')
+
+rlwrap = {} # namespace for this module in web context
+
+ffi = require('ffi')
+fs = require('fs')
+
+rllib = ffi.Library(RL_LIB, {
+ 'readline': ['string', ['string']],
+ 'add_history': ['int', ['string']]})
+
+rl_history_loaded = false
+
+exports.readline = rlwrap.readline = (prompt = 'user> ') ->
+ if !rl_history_loaded
+ rl_history_loaded = true
+ lines = []
+ if fs.existsSync(HISTORY_FILE)
+ lines = fs.readFileSync(HISTORY_FILE).toString().split("\n");
+
+ # Max of 2000 lines
+ lines = lines[Math.max(lines.length - 2000, 0)..]
+ rllib.add_history(line) for line in lines when line != ""
+
+ line = rllib.readline prompt
+ if line
+ rllib.add_history line
+ fs.appendFileSync HISTORY_FILE, line + "\n"
+
+ line
+
+# vim: ts=2:sw=2
diff --git a/coffee/package.json b/coffee/package.json
new file mode 100644
index 0000000..859ec4a
--- /dev/null
+++ b/coffee/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "mal",
+ "version": "0.0.1",
+ "description": "Make a Lisp (mal) language implemented in CoffeeScript",
+ "dependencies": {
+ "ffi": "1.2.x",
+ "coffee-script": "~1.8"
+ }
+}
diff --git a/coffee/printer.coffee b/coffee/printer.coffee
new file mode 100644
index 0000000..9f56e2e
--- /dev/null
+++ b/coffee/printer.coffee
@@ -0,0 +1,25 @@
+types = require "./types.coffee"
+
+exports.println = (args...) -> console.log(args.join(" ")) || null
+
+exports._pr_str = _pr_str = (obj, print_readably=true) ->
+ _r = print_readably
+ switch types._obj_type obj
+ when 'list' then '(' + obj.map((e) -> _pr_str(e,_r)).join(' ') + ')'
+ when 'vector' then '[' + obj.map((e) -> _pr_str(e,_r)).join(' ') + ']'
+ when 'hash-map'
+ ret = []
+ ret.push(_pr_str(k,_r), _pr_str(v,_r)) for k,v of obj
+ '{' + ret.join(' ') + '}'
+ when 'string'
+ if _r then '"' + (obj.replace(/\\/g, '\\\\')
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, '\\n')) + '"'
+ else obj
+ when 'keyword' then ":" + obj.slice(1)
+ when 'symbol' then obj.name
+ when 'nil' then 'nil'
+ when 'atom' then "(atom " + _pr_str(obj.val,_r) + ")"
+ else obj.toString()
+
+# vim: ts=2:sw=2
diff --git a/coffee/reader.coffee b/coffee/reader.coffee
new file mode 100644
index 0000000..83d24d2
--- /dev/null
+++ b/coffee/reader.coffee
@@ -0,0 +1,87 @@
+types = require "./types.coffee"
+_symbol = types._symbol
+
+
+class Reader
+ constructor: (@tokens) -> @position = 0
+ next: -> @tokens[@position++]
+ peek: -> @tokens[@position]
+ skip: ->
+ @position++
+ @
+
+tokenize = (str) ->
+ re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g
+ results = []
+ while (match = re.exec(str)[1]) != ""
+ continue if match[0] == ';'
+ results.push(match)
+ results
+
+read_atom = (rdr) ->
+ token = rdr.next()
+ if token.match /^-?[0-9]+$/ then parseInt token,10
+ else if token.match /^-?[0-9][0-9.]*$/ then parseFloat token,10
+ else if token[0] == '"'
+ token.slice(1, token.length-1)
+ .replace(/\\"/g, '"')
+ .replace(/\\n/g, "\n")
+ else if token[0] == ':' then types._keyword(token[1..])
+ else if token == "nil" then null
+ else if token == "true" then true
+ else if token == "false" then false
+ else _symbol(token)
+
+read_list = (rdr, start='(', end=')') ->
+ ast = []
+ token = rdr.next()
+ throw new Error "expected '" + start + "'" if token != start
+ while (token = rdr.peek()) != end
+ throw new Error "expected '" + end + "', got EOF" if !token
+ ast.push read_form rdr
+ rdr.next()
+ ast
+
+read_vector = (rdr) ->
+ types._vector(read_list(rdr, '[', ']')...)
+
+read_hash_map = (rdr) ->
+ types._hash_map(read_list(rdr, '{', '}')...)
+
+read_form = (rdr) ->
+ token = rdr.peek()
+ switch token
+ when '\'' then [_symbol('quote'), read_form(rdr.skip())]
+ when '`' then [_symbol('quasiquote'), read_form(rdr.skip())]
+ when '~' then [_symbol('unquote'), read_form(rdr.skip())]
+ when '~@' then [_symbol('splice-unquote'), read_form(rdr.skip())]
+ when '^'
+ meta = read_form(rdr.skip())
+ [_symbol('with-meta'), read_form(rdr), meta]
+ when '@' then [_symbol('deref'), read_form(rdr.skip())]
+
+ # list
+ when ')' then throw new Error "unexpected ')'"
+ when '(' then read_list(rdr)
+ # vector
+ when ']' then throw new Error "unexpected ']'"
+ when '[' then read_vector(rdr)
+ # hash-map
+ when '}' then throw new Error "unexpected '}'"
+ when '{' then read_hash_map(rdr)
+ # atom
+ else read_atom(rdr)
+
+
+exports.BlankException = BlankException = (msg) -> null
+
+exports.read_str = read_str = (str) ->
+ tokens = tokenize(str)
+ throw new BlankException() if tokens.length == 0
+ read_form(new Reader(tokens))
+
+#console.log read_str "(1 \"two\" three)"
+#console.log read_str "[1 2 3]"
+#console.log read_str '{"abc" 123 "def" 456}'
+
+# vim: ts=2:sw=2
diff --git a/coffee/step0_repl.coffee b/coffee/step0_repl.coffee
new file mode 100644
index 0000000..4fa9e40
--- /dev/null
+++ b/coffee/step0_repl.coffee
@@ -0,0 +1,20 @@
+readline = require "./node_readline.coffee"
+
+# read
+READ = (str) -> str
+
+# eval
+EVAL = (ast, env) -> ast
+
+# print
+PRINT = (exp) -> exp
+
+# repl
+rep = (str) -> PRINT(EVAL(READ(str), {}))
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ console.log rep line
+
+# vim: ts=2:sw=2
diff --git a/coffee/step1_read_print.coffee b/coffee/step1_read_print.coffee
new file mode 100644
index 0000000..28edd1b
--- /dev/null
+++ b/coffee/step1_read_print.coffee
@@ -0,0 +1,27 @@
+readline = require "./node_readline.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+EVAL = (ast, env) -> ast
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+rep = (str) -> PRINT(EVAL(READ(str), {}))
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step2_eval.coffee b/coffee/step2_eval.coffee
new file mode 100644
index 0000000..1b3438e
--- /dev/null
+++ b/coffee/step2_eval.coffee
@@ -0,0 +1,52 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env[ast.name]
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ [f, args...] = eval_ast ast, env
+ f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = {}
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+repl_env["+"] = (a,b) -> a+b
+repl_env["-"] = (a,b) -> a-b
+repl_env["*"] = (a,b) -> a*b
+repl_env["/"] = (a,b) -> a/b
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step3_env.coffee b/coffee/step3_env.coffee
new file mode 100644
index 0000000..1446197
--- /dev/null
+++ b/coffee/step3_env.coffee
@@ -0,0 +1,63 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ EVAL(a2, let_env)
+ else
+ [f, args...] = eval_ast ast, env
+ f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+repl_env.set types._symbol("+"), (a,b) -> a+b
+repl_env.set types._symbol("-"), (a,b) -> a-b
+repl_env.set types._symbol("*"), (a,b) -> a*b
+repl_env.set types._symbol("/"), (a,b) -> a/b
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step4_if_fn_do.coffee b/coffee/step4_if_fn_do.coffee
new file mode 100644
index 0000000..ef33478
--- /dev/null
+++ b/coffee/step4_if_fn_do.coffee
@@ -0,0 +1,76 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ EVAL(a2, let_env)
+ when "do"
+ el = eval_ast(ast[1..], env)
+ el[el.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then EVAL(a3, env) else null
+ else
+ EVAL(a2, env)
+ when "fn*"
+ (args...) -> EVAL(a2, new Env(env, a1, args))
+ else
+ [f, args...] = eval_ast ast, env
+ f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+
+# core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step5_tco.coffee b/coffee/step5_tco.coffee
new file mode 100644
index 0000000..fa5aced
--- /dev/null
+++ b/coffee/step5_tco.coffee
@@ -0,0 +1,82 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+
+# core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step6_file.coffee b/coffee/step6_file.coffee
new file mode 100644
index 0000000..feafc63
--- /dev/null
+++ b/coffee/step6_file.coffee
@@ -0,0 +1,90 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+repl_env.set types._symbol('eval'), (ast) -> EVAL(ast, repl_env)
+repl_env.set types._symbol('*ARGV*'), []
+
+# 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 process? && process.argv.length > 2
+ repl_env.set types._symbol('*ARGV*'), process.argv[3..]
+ rep('(load-file "' + process.argv[2] + '")')
+ process.exit 0
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step7_quote.coffee b/coffee/step7_quote.coffee
new file mode 100644
index 0000000..7652a79
--- /dev/null
+++ b/coffee/step7_quote.coffee
@@ -0,0 +1,106 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+is_pair = (x) -> types._sequential_Q(x) && x.length > 0
+
+quasiquote = (ast) ->
+ if !is_pair(ast) then [types._symbol('quote'), ast]
+ else if ast[0].name == 'unquote' then ast[1]
+ else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote'
+ [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])]
+ else
+ [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])]
+
+
+
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "quote"
+ return a1
+ when "quasiquote"
+ ast = quasiquote(a1)
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+repl_env.set types._symbol('eval'), (ast) -> EVAL(ast, repl_env)
+repl_env.set types._symbol('*ARGV*'), []
+
+# 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 process? && process.argv.length > 2
+ repl_env.set types._symbol('*ARGV*'), process.argv[3..]
+ rep('(load-file "' + process.argv[2] + '")')
+ process.exit 0
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step8_macros.coffee b/coffee/step8_macros.coffee
new file mode 100644
index 0000000..6150d2e
--- /dev/null
+++ b/coffee/step8_macros.coffee
@@ -0,0 +1,126 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+is_pair = (x) -> types._sequential_Q(x) && x.length > 0
+
+quasiquote = (ast) ->
+ if !is_pair(ast) then [types._symbol('quote'), ast]
+ else if ast[0].name == 'unquote' then ast[1]
+ else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote'
+ [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])]
+ else
+ [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])]
+
+is_macro_call = (ast, env) ->
+ return types._list_Q(ast) && types._symbol_Q(ast[0]) &&
+ env.find(ast[0]) && env.get(ast[0]).__ismacro__
+
+macroexpand = (ast, env) ->
+ while is_macro_call(ast, env)
+ ast = env.get(ast[0])(ast[1..]...)
+ ast
+
+
+
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ ast = macroexpand ast, env
+ if !types._list_Q ast then return ast
+
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "quote"
+ return a1
+ when "quasiquote"
+ ast = quasiquote(a1)
+ when "defmacro!"
+ f = EVAL(a2, env)
+ f.__ismacro__ = true
+ return env.set(a1, f)
+ when "macroexpand"
+ return macroexpand(a1, env)
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+repl_env.set types._symbol('eval'), (ast) -> EVAL(ast, repl_env)
+repl_env.set types._symbol('*ARGV*'), []
+
+# 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 process? && process.argv.length > 2
+ repl_env.set types._symbol('*ARGV*'), process.argv[3..]
+ rep('(load-file "' + process.argv[2] + '")')
+ process.exit 0
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/step9_try.coffee b/coffee/step9_try.coffee
new file mode 100644
index 0000000..d8f6f43
--- /dev/null
+++ b/coffee/step9_try.coffee
@@ -0,0 +1,134 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+is_pair = (x) -> types._sequential_Q(x) && x.length > 0
+
+quasiquote = (ast) ->
+ if !is_pair(ast) then [types._symbol('quote'), ast]
+ else if ast[0].name == 'unquote' then ast[1]
+ else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote'
+ [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])]
+ else
+ [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])]
+
+is_macro_call = (ast, env) ->
+ return types._list_Q(ast) && types._symbol_Q(ast[0]) &&
+ env.find(ast[0]) && env.get(ast[0]).__ismacro__
+
+macroexpand = (ast, env) ->
+ while is_macro_call(ast, env)
+ ast = env.get(ast[0])(ast[1..]...)
+ ast
+
+
+
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ ast = macroexpand ast, env
+ if !types._list_Q ast then return ast
+
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "quote"
+ return a1
+ when "quasiquote"
+ ast = quasiquote(a1)
+ when "defmacro!"
+ f = EVAL(a2, env)
+ f.__ismacro__ = true
+ return env.set(a1, f)
+ when "macroexpand"
+ return macroexpand(a1, env)
+ when "try*"
+ try return EVAL(a1, env)
+ catch exc
+ if a2 && a2[0].name == "catch*"
+ if exc instanceof Error then exc = exc.message
+ return EVAL a2[2], new Env(env, [a2[1]], [exc])
+ else
+ throw exc
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+repl_env.set types._symbol('eval'), (ast) -> EVAL(ast, repl_env)
+repl_env.set types._symbol('*ARGV*'), []
+
+# 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 process? && process.argv.length > 2
+ repl_env.set types._symbol('*ARGV*'), process.argv[3..]
+ rep('(load-file "' + process.argv[2] + '")')
+ process.exit 0
+
+# repl loop
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/coffee/stepA_mal.coffee b/coffee/stepA_mal.coffee
new file mode 100644
index 0000000..751f9ad
--- /dev/null
+++ b/coffee/stepA_mal.coffee
@@ -0,0 +1,142 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+is_pair = (x) -> types._sequential_Q(x) && x.length > 0
+
+quasiquote = (ast) ->
+ if !is_pair(ast) then [types._symbol('quote'), ast]
+ else if ast[0].name == 'unquote' then ast[1]
+ else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote'
+ [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])]
+ else
+ [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])]
+
+is_macro_call = (ast, env) ->
+ return types._list_Q(ast) && types._symbol_Q(ast[0]) &&
+ env.find(ast[0]) && env.get(ast[0]).__ismacro__
+
+macroexpand = (ast, env) ->
+ while is_macro_call(ast, env)
+ ast = env.get(ast[0])(ast[1..]...)
+ ast
+
+
+
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ ast = macroexpand ast, env
+ if !types._list_Q ast then return ast
+
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "quote"
+ return a1
+ when "quasiquote"
+ ast = quasiquote(a1)
+ when "defmacro!"
+ f = EVAL(a2, env)
+ f.__ismacro__ = true
+ return env.set(a1, f)
+ when "macroexpand"
+ return macroexpand(a1, env)
+ when "try*"
+ try return EVAL(a1, env)
+ catch exc
+ if a2 && a2[0].name == "catch*"
+ if exc instanceof Error then exc = exc.message
+ return EVAL a2[2], new Env(env, [a2[1]], [exc])
+ else
+ throw exc
+ when "js*"
+ res = eval(a1.toString())
+ return if typeof(res) == 'undefined' then null else res
+ when "."
+ el = eval_ast(ast[2..], env)
+ return eval(a1.toString())(el...)
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set types._symbol(k), v for k,v of core.ns
+repl_env.set types._symbol('eval'), (ast) -> EVAL(ast, repl_env)
+repl_env.set types._symbol('*ARGV*'), []
+
+# core.mal: defined using the language itself
+rep("(def! *host-language* \"CoffeeScript\")")
+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 process? && process.argv.length > 2
+ repl_env.set types._symbol('*ARGV*'), process.argv[3..]
+ rep('(load-file "' + process.argv[2] + '")')
+ process.exit 0
+
+# repl loop
+rep("(println (str \"Mal [\" *host-language* \"]\"))")
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2
diff --git a/js/tests/step9_interop.mal b/coffee/tests/stepA_mal.mal
index f785292..f785292 100644
--- a/js/tests/step9_interop.mal
+++ b/coffee/tests/stepA_mal.mal
diff --git a/coffee/types.coffee b/coffee/types.coffee
new file mode 100644
index 0000000..d732064
--- /dev/null
+++ b/coffee/types.coffee
@@ -0,0 +1,122 @@
+Env = require("./env.coffee").Env
+
+E = exports
+
+# General functions
+E._obj_type = _obj_type = (obj) ->
+ if _symbol_Q(obj) then 'symbol'
+ else if _list_Q(obj) then 'list'
+ else if _vector_Q(obj) then 'vector'
+ else if _hash_map_Q(obj) then 'hash-map'
+ else if _nil_Q(obj) then 'nil'
+ else if _true_Q(obj) then 'true'
+ else if _false_Q(obj) then 'false'
+ else if _atom_Q(obj) then 'atom'
+ else
+ switch typeof obj
+ when 'number' then 'number'
+ when 'function' then 'function'
+ when 'string'
+ if obj[0] == '\u029e' then 'keyword' else 'string'
+ else throw new Error "Unknown type '" + typeof(obj) + "'"
+
+E._sequential_Q = _sequential_Q = (o) -> _list_Q(o) or _vector_Q(o)
+
+E._equal_Q = _equal_Q = (a,b) ->
+ [ota, otb] = [_obj_type(a), _obj_type(b)]
+ if !(ota == otb or (_sequential_Q(a) && _sequential_Q(b)))
+ return false
+ switch (ota)
+ when 'symbol' then a.name == b.name
+ when 'list', 'vector'
+ return false if a.length != b.length
+ for av,i in a
+ return false if !_equal_Q(av, b[i])
+ true
+ when 'hash-map'
+ akeys = (key for key of a)
+ bkeys = (key for key of b)
+ return false if akeys.length != bkeys.length
+ for akey,i in akeys
+ bkey = bkeys[i]
+ return false if akey != bkey
+ return false if !_equal_Q(a[akey], b[bkey])
+ true
+ else a == b
+
+E._clone = _clone = (obj) ->
+ switch _obj_type(obj)
+ when 'list' then obj[0..-1]
+ when 'vector' then _vector(obj[0..-1]...)
+ when 'hash-map'
+ new_obj = {}
+ new_obj[k] = v for k,v of obj
+ new_obj
+ when 'function'
+ new_obj = (args...) -> obj(args...)
+ new_obj[k] = v for k,v of obj
+ new_obj
+ else throw new Error "clone called on non-collection" + _obj_type(obj)
+
+
+# Scalars
+E._nil_Q = _nil_Q = (o) -> o == null
+E._true_Q = _true_Q = (o) -> o == true
+E._false_Q = _false_Q = (o) -> o == false
+
+# Symbols
+class Symbol
+ constructor: (@name) ->
+E._symbol = (str) -> new Symbol str
+E._symbol_Q = _symbol_Q = (o) -> o instanceof Symbol
+
+# Keywords
+E._keyword = _keyword = (str) -> "\u029e" + str
+E._keyword_Q = _keyword_Q = (o) ->
+ typeof o == 'string' && o[0] == "\u029e"
+
+# Functions
+E._function = (evalfn, ast, env, params) ->
+ fn = (args...) -> evalfn(ast, new Env(env, params, args))
+ fn.__ast__ = ast
+ fn.__gen_env__ = (args) -> new Env(env, params, args)
+ fn.__ismacro__ = false
+ fn
+E._function_Q = _function_Q = (o) -> !!o.__ast__
+
+# Lists
+E._list_Q = _list_Q = (o) -> Array.isArray(o) && !o.__isvector__
+
+# Vectors
+E._vector = _vector = (args...) ->
+ v = args
+ v.__isvector__ = true
+ v
+E._vector_Q = _vector_Q = (o) -> Array.isArray(o) && !!o.__isvector__
+
+# Hash Maps
+E._hash_map = (args...) ->
+ args = [{}].concat args
+ _assoc_BANG(args...)
+E._assoc_BANG = _assoc_BANG = (hm, args...) ->
+ if args.length %% 2 == 1
+ throw new Error "Odd number of hash map arguments"
+ hm[k] = args[i+1] for k, i in args when i %% 2 == 0
+ hm
+E._dissoc_BANG = (hm, args...) ->
+ delete hm[k] for k, i in args
+ hm
+E._hash_map_Q = _hash_map_Q = (o) ->
+ typeof o == "object" && !Array.isArray(o) &&
+ !(o == null) &&
+ !(o instanceof Symbol) &&
+ !(o instanceof Atom)
+
+
+# Atoms
+class Atom
+ constructor: (@val) ->
+E._atom = (val) -> new Atom val
+E._atom_Q = _atom_Q = (o) -> o instanceof Atom
+
+# vim: ts=2:sw=2
diff --git a/core.mal b/core.mal
index 9cd41eb..2c4fc82 100644
--- a/core.mal
+++ b/core.mal
@@ -82,18 +82,3 @@
(list form x))
`(->> (->> ~x ~form) ~@more))))))
-(if (= "make" *host-language*)
- (defmacro! time
- (fn* (exp)
- `(let* [start_FIXME (time-secs)
- ret_FIXME ~exp]
- (do
- (prn (str "Elapsed time: " (- (time-secs) start_FIXME) "000 msecs"))
- ret_FIXME))))
- (defmacro! time
- (fn* (exp)
- `(let* [start_FIXME (time-ms)
- ret_FIXME ~exp]
- (do
- (prn (str "Elapsed time: " (- (time-ms) start_FIXME) " msecs"))
- ret_FIXME)))))
diff --git a/cs/Makefile b/cs/Makefile
index b7eb023..70d54ad 100644
--- a/cs/Makefile
+++ b/cs/Makefile
@@ -5,7 +5,7 @@ DEBUG =
TESTS =
SOURCES_BASE = readline.cs types.cs reader.cs printer.cs
-SOURCES_LISP = env.cs core.cs stepA_more.cs
+SOURCES_LISP = env.cs core.cs stepA_mal.cs
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
OTHER_SOURCES = getline.cs
@@ -14,7 +14,7 @@ OTHER_SOURCES = getline.cs
SRCS = step0_repl.cs step1_read_print.cs step2_eval.cs step3_env.cs \
step4_if_fn_do.cs step5_tco.cs step6_file.cs step7_quote.cs \
- step8_macros.cs stepA_more.cs
+ step8_macros.cs step9_try.cs stepA_mal.cs
LIB_SRCS = $(filter-out step%,$(OTHER_SOURCES) $(SOURCES))
diff --git a/cs/core.cs b/cs/core.cs
index 7cfb772..3e40681 100644
--- a/cs/core.cs
+++ b/cs/core.cs
@@ -3,14 +3,14 @@ using System.IO;
using System.Collections.Generic;
using MalVal = Mal.types.MalVal;
using MalConstant = Mal.types.MalConstant;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalSymbol = Mal.types.MalSymbol;
using MalString = Mal.types.MalString;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
using MalAtom = Mal.types.MalAtom;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
namespace Mal {
public class core {
@@ -19,93 +19,105 @@ namespace Mal {
static MalConstant False = Mal.types.False;
// Errors/Exceptions
- static public MalFunction mal_throw = new MalFunction(
+ static public MalFunc mal_throw = new MalFunc(
a => { throw new Mal.types.MalException(a[0]); });
// Scalar functions
- static MalFunction nil_Q = new MalFunction(
+ static MalFunc nil_Q = new MalFunc(
a => a[0] == Nil ? True : False);
- static MalFunction true_Q = new MalFunction(
+ static MalFunc true_Q = new MalFunc(
a => a[0] == True ? True : False);
- static MalFunction false_Q = new MalFunction(
+ static MalFunc false_Q = new MalFunc(
a => a[0] == False ? True : False);
- static MalFunction symbol_Q = new MalFunction(
+ static MalFunc symbol_Q = new MalFunc(
a => a[0] is MalSymbol ? True : False);
+ static MalFunc keyword = new MalFunc(
+ a => new MalString("\u029e" + ((MalString)a[0]).getValue()));
+
+ static MalFunc keyword_Q = new MalFunc(
+ a => {
+ if (a[0] is MalString &&
+ ((MalString)a[0]).getValue()[0] == '\u029e') {
+ return True;
+ } else {
+ return False;
+ }
+ } );
+
// Number functions
- static MalFunction time_ms = new MalFunction(
- a => new MalInteger((int)(
- DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)));
+ static MalFunc time_ms = new MalFunc(
+ a => new MalInt(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond));
// String functions
- static public MalFunction pr_str = new MalFunction(
+ static public MalFunc pr_str = new MalFunc(
a => new MalString(printer._pr_str_args(a, " ", true)) );
- static public MalFunction str = new MalFunction(
+ static public MalFunc str = new MalFunc(
a => new MalString(printer._pr_str_args(a, "", false)) );
- static public MalFunction prn = new MalFunction(
+ static public MalFunc prn = new MalFunc(
a => {
Console.WriteLine(printer._pr_str_args(a, " ", true));
return Nil;
} );
- static public MalFunction println = new MalFunction(
+ static public MalFunc println = new MalFunc(
a => {
Console.WriteLine(printer._pr_str_args(a, " ", false));
return Nil;
} );
- static public MalFunction mal_readline = new MalFunction(
+ static public MalFunc mal_readline = new MalFunc(
a => {
var line = readline.Readline(((MalString)a[0]).getValue());
if (line == null) { return types.Nil; }
else { return new MalString(line); }
} );
- static public MalFunction read_string = new MalFunction(
+ static public MalFunc read_string = new MalFunc(
a => reader.read_str(((MalString)a[0]).getValue()));
- static public MalFunction slurp = new MalFunction(
+ static public MalFunc slurp = new MalFunc(
a => new MalString(File.ReadAllText(
((MalString)a[0]).getValue())));
// List/Vector functions
- static public MalFunction list_Q = new MalFunction(
+ static public MalFunc list_Q = new MalFunc(
a => a[0].GetType() == typeof(MalList) ? True : False);
- static public MalFunction vector_Q = new MalFunction(
+ static public MalFunc vector_Q = new MalFunc(
a => a[0].GetType() == typeof(MalVector) ? True : False);
// HashMap functions
- static public MalFunction hash_map_Q = new MalFunction(
+ static public MalFunc hash_map_Q = new MalFunc(
a => a[0].GetType() == typeof(MalHashMap) ? True : False);
- static MalFunction contains_Q = new MalFunction(
+ static MalFunc contains_Q = new MalFunc(
a => {
string key = ((MalString)a[1]).getValue();
var dict = ((MalHashMap)a[0]).getValue();
return dict.ContainsKey(key) ? True : False;
});
- static MalFunction assoc = new MalFunction(
+ static MalFunc assoc = new MalFunc(
a => {
var new_hm = ((MalHashMap)a[0]).copy();
return new_hm.assoc_BANG((MalList)a.slice(1));
});
- static MalFunction dissoc = new MalFunction(
+ static MalFunc dissoc = new MalFunc(
a => {
var new_hm = ((MalHashMap)a[0]).copy();
return new_hm.dissoc_BANG((MalList)a.slice(1));
});
- static MalFunction get = new MalFunction(
+ static MalFunc get = new MalFunc(
a => {
string key = ((MalString)a[1]).getValue();
if (a[0] == Nil) {
@@ -116,7 +128,7 @@ namespace Mal {
}
});
- static MalFunction keys = new MalFunction(
+ static MalFunc keys = new MalFunc(
a => {
var dict = ((MalHashMap)a[0]).getValue();
MalList key_lst = new MalList();
@@ -126,7 +138,7 @@ namespace Mal {
return key_lst;
});
- static MalFunction vals = new MalFunction(
+ static MalFunc vals = new MalFunc(
a => {
var dict = ((MalHashMap)a[0]).getValue();
MalList val_lst = new MalList();
@@ -137,10 +149,10 @@ namespace Mal {
});
// Sequence functions
- static public MalFunction sequential_Q = new MalFunction(
+ static public MalFunc sequential_Q = new MalFunc(
a => a[0] is MalList ? True : False);
- static MalFunction cons = new MalFunction(
+ static MalFunc cons = new MalFunc(
a => {
var lst = new List<MalVal>();
lst.Add(a[0]);
@@ -148,7 +160,7 @@ namespace Mal {
return (MalVal)new MalList(lst);
});
- static MalFunction concat = new MalFunction(
+ static MalFunc concat = new MalFunc(
a => {
if (a.size() == 0) { return new MalList(); }
var lst = new List<MalVal>();
@@ -159,22 +171,34 @@ namespace Mal {
return (MalVal)new MalList(lst);
});
- static MalFunction nth = new MalFunction(
- a => ((MalList)a[0])[ ((MalInteger)a[1]).getValue() ]);
+ static MalFunc nth = new MalFunc(
+ a => {
+ var idx = (int)((MalInt)a[1]).getValue();
+ if (idx < ((MalList)a[0]).size()) {
+ return ((MalList)a[0])[idx];
+ } else {
+ throw new Mal.types.MalException(
+ "nth: index out of range");
+ }
+ });
- static MalFunction first = new MalFunction(
+ static MalFunc first = new MalFunc(
a => ((MalList)a[0])[0]);
- static MalFunction rest = new MalFunction(
+ static MalFunc rest = new MalFunc(
a => ((MalList)a[0]).rest());
- static MalFunction empty_Q = new MalFunction(
+ static MalFunc empty_Q = new MalFunc(
a => ((MalList)a[0]).size() == 0 ? True : False);
- static MalFunction count = new MalFunction(
- a => new MalInteger(((MalList)a[0]).size()));
+ static MalFunc count = new MalFunc(
+ a => {
+ return (a[0] == Nil)
+ ? new MalInt(0)
+ :new MalInt(((MalList)a[0]).size());
+ });
- static MalFunction conj = new MalFunction(
+ static MalFunc conj = new MalFunc(
a => {
var src_lst = ((MalList)a[0]).getValue();
var new_lst = new List<MalVal>();
@@ -194,18 +218,18 @@ namespace Mal {
// General list related functions
- static MalFunction apply = new MalFunction(
+ static MalFunc apply = new MalFunc(
a => {
- var f = (MalFunction)a[0];
+ var f = (MalFunc)a[0];
var lst = new List<MalVal>();
lst.AddRange(a.slice(1,a.size()-1).getValue());
lst.AddRange(((MalList)a[a.size()-1]).getValue());
return f.apply(new MalList(lst));
});
- static MalFunction map = new MalFunction(
+ static MalFunc map = new MalFunc(
a => {
- MalFunction f = (MalFunction) a[0];
+ MalFunc f = (MalFunc) a[0];
var src_lst = ((MalList)a[1]).getValue();
var new_lst = new List<MalVal>();
for(int i=0; i<src_lst.Count; i++) {
@@ -216,27 +240,27 @@ namespace Mal {
// Metadata functions
- static MalFunction meta = new MalFunction(
+ static MalFunc meta = new MalFunc(
a => a[0].getMeta());
- static MalFunction with_meta = new MalFunction(
+ static MalFunc with_meta = new MalFunc(
a => ((MalVal)a[0]).copy().setMeta(a[1]));
// Atom functions
- static MalFunction atom_Q = new MalFunction(
+ static MalFunc atom_Q = new MalFunc(
a => a[0] is MalAtom ? True : False);
- static MalFunction deref = new MalFunction(
+ static MalFunc deref = new MalFunc(
a => ((MalAtom)a[0]).getValue());
- static MalFunction reset_BANG = new MalFunction(
+ static MalFunc reset_BANG = new MalFunc(
a => ((MalAtom)a[0]).setValue(a[1]));
- static MalFunction swap_BANG = new MalFunction(
+ static MalFunc swap_BANG = new MalFunc(
a => {
MalAtom atm = (MalAtom)a[0];
- MalFunction f = (MalFunction)a[1];
+ MalFunc f = (MalFunc)a[1];
var new_lst = new List<MalVal>();
new_lst.Add(atm.getValue());
new_lst.AddRange(((MalList)a.slice(2)).getValue());
@@ -247,13 +271,16 @@ namespace Mal {
static public Dictionary<string, MalVal> ns =
new Dictionary<string, MalVal> {
- {"=", new MalFunction(
+ {"=", new MalFunc(
a => Mal.types._equal_Q(a[0], a[1]) ? True : False)},
{"throw", mal_throw},
{"nil?", nil_Q},
{"true?", true_Q},
{"false?", false_Q},
+ {"symbol", new MalFunc(a => new MalSymbol((MalString)a[0]))},
{"symbol?", symbol_Q},
+ {"keyword", keyword},
+ {"keyword?", keyword_Q},
{"pr-str", pr_str},
{"str", str},
@@ -262,21 +289,21 @@ namespace Mal {
{"readline", mal_readline},
{"read-string", read_string},
{"slurp", slurp},
- {"<", new MalFunction(a => (MalInteger)a[0] < (MalInteger)a[1])},
- {"<=", new MalFunction(a => (MalInteger)a[0] <= (MalInteger)a[1])},
- {">", new MalFunction(a => (MalInteger)a[0] > (MalInteger)a[1])},
- {">=", new MalFunction(a => (MalInteger)a[0] >= (MalInteger)a[1])},
- {"+", new MalFunction(a => (MalInteger)a[0] + (MalInteger)a[1])},
- {"-", new MalFunction(a => (MalInteger)a[0] - (MalInteger)a[1])},
- {"*", new MalFunction(a => (MalInteger)a[0] * (MalInteger)a[1])},
- {"/", new MalFunction(a => (MalInteger)a[0] / (MalInteger)a[1])},
+ {"<", new MalFunc(a => (MalInt)a[0] < (MalInt)a[1])},
+ {"<=", new MalFunc(a => (MalInt)a[0] <= (MalInt)a[1])},
+ {">", new MalFunc(a => (MalInt)a[0] > (MalInt)a[1])},
+ {">=", new MalFunc(a => (MalInt)a[0] >= (MalInt)a[1])},
+ {"+", new MalFunc(a => (MalInt)a[0] + (MalInt)a[1])},
+ {"-", new MalFunc(a => (MalInt)a[0] - (MalInt)a[1])},
+ {"*", new MalFunc(a => (MalInt)a[0] * (MalInt)a[1])},
+ {"/", new MalFunc(a => (MalInt)a[0] / (MalInt)a[1])},
{"time-ms", time_ms},
- {"list", new MalFunction(a => new MalList(a.getValue()))},
+ {"list", new MalFunc(a => new MalList(a.getValue()))},
{"list?", list_Q},
- {"vector", new MalFunction(a => new MalVector(a.getValue()))},
+ {"vector", new MalFunc(a => new MalVector(a.getValue()))},
{"vector?", vector_Q},
- {"hash-map", new MalFunction(a => new MalHashMap(a))},
+ {"hash-map", new MalFunc(a => new MalHashMap(a))},
{"map?", hash_map_Q},
{"contains?", contains_Q},
{"assoc", assoc},
@@ -299,7 +326,7 @@ namespace Mal {
{"with-meta", with_meta},
{"meta", meta},
- {"atom", new MalFunction(a => new MalAtom(a[0]))},
+ {"atom", new MalFunc(a => new MalAtom(a[0]))},
{"atom?", atom_Q},
{"deref", deref},
{"reset!", reset_BANG},
diff --git a/cs/env.cs b/cs/env.cs
index cb5318f..39ab100 100644
--- a/cs/env.cs
+++ b/cs/env.cs
@@ -26,8 +26,8 @@ namespace Mal {
}
}
- public Env find(string key) {
- if (data.ContainsKey(key)) {
+ public Env find(MalSymbol key) {
+ if (data.ContainsKey(key.getName())) {
return this;
} else if (outer != null) {
return outer.find(key);
@@ -36,18 +36,18 @@ namespace Mal {
}
}
- public MalVal get(string key) {
+ public MalVal get(MalSymbol key) {
Env e = find(key);
if (e == null) {
throw new Mal.types.MalException(
- "'" + key + "' not found");
+ "'" + key.getName() + "' not found");
} else {
- return e.data[key];
+ return e.data[key.getName()];
}
}
- public Env set(string key, MalVal value) {
- data[key] = value;
+ public Env set(MalSymbol key, MalVal value) {
+ data[key.getName()] = value;
return this;
}
}
diff --git a/cs/interop.cs b/cs/interop.cs
new file mode 100644
index 0000000..e383280
--- /dev/null
+++ b/cs/interop.cs
@@ -0,0 +1,66 @@
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CSharp;
+
+public static class EvalProvider
+{
+ public static Func<T, TResult> CreateEvalMethod<T, TResult>(string code, string[] usingStatements = null, string[] assemblies = null)
+ {
+ Type returnType = typeof(TResult);
+ Type inputType = typeof(T);
+
+ var includeUsings = new HashSet<string>(new[] { "System" });
+ includeUsings.Add(returnType.Namespace);
+ includeUsings.Add(inputType.Namespace);
+ if (usingStatements != null)
+ foreach (var usingStatement in usingStatements)
+ includeUsings.Add(usingStatement);
+
+ using (CSharpCodeProvider compiler = new CSharpCodeProvider())
+ {
+ var name = "F" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+ var includeAssemblies = new HashSet<string>(new[] { "system.dll" });
+ if (assemblies != null)
+ foreach (var assembly in assemblies)
+ includeAssemblies.Add(assembly);
+
+ var parameters = new CompilerParameters(includeAssemblies.ToArray())
+ {
+ GenerateInMemory = true
+ };
+
+ string source = string.Format(@"
+{0}
+namespace {1}
+{{
+ public static class EvalClass
+ {{
+ public static {2} Eval({3} arg)
+ {{
+ {4}
+ }}
+ }}
+}}", GetUsing(includeUsings), name, returnType.Name, inputType.Name, code);
+
+ var compilerResult = compiler.CompileAssemblyFromSource(parameters, source);
+ var compiledAssembly = compilerResult.CompiledAssembly;
+ var type = compiledAssembly.GetType(string.Format("{0}.EvalClass", name));
+ var method = type.GetMethod("Eval");
+ return (Func<T, TResult>)Delegate.CreateDelegate(typeof(Func<T, TResult>), method);
+ }
+ }
+
+ private static string GetUsing(HashSet<string> usingStatements)
+ {
+ StringBuilder result = new StringBuilder();
+ foreach (string usingStatement in usingStatements)
+ {
+ result.AppendLine(string.Format("using {0};", usingStatement));
+ }
+ return result.ToString();
+ }
+}
+
diff --git a/cs/printer.cs b/cs/printer.cs
index b5e8c63..8b10dd1 100644
--- a/cs/printer.cs
+++ b/cs/printer.cs
@@ -20,7 +20,9 @@ namespace Mal {
string delim, bool print_readably) {
List<string> strs = new List<string>();
foreach (KeyValuePair<string, MalVal> entry in value) {
- if (print_readably) {
+ if (entry.Key.Length > 0 && entry.Key[0] == '\u029e') {
+ strs.Add(":" + entry.Key.Substring(1));
+ } else if (print_readably) {
strs.Add("\"" + entry.Key.ToString() + "\"");
} else {
strs.Add(entry.Key.ToString());
diff --git a/cs/reader.cs b/cs/reader.cs
index 8ab53da..10973aa 100644
--- a/cs/reader.cs
+++ b/cs/reader.cs
@@ -53,7 +53,7 @@ namespace Mal {
public static MalVal read_atom(Reader rdr) {
string token = rdr.next();
- string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|(^[^""]*$)";
+ string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|:(.*)|(^[^""]*$)";
Regex regex = new Regex(pattern);
Match match = regex.Match(token);
//Console.WriteLine("token: ^" + token + "$");
@@ -61,7 +61,7 @@ namespace Mal {
throw new ParseError("unrecognized token '" + token + "'");
}
if (match.Groups[1].Value != String.Empty) {
- return new Mal.types.MalInteger(int.Parse(match.Groups[1].Value));
+ return new Mal.types.MalInt(int.Parse(match.Groups[1].Value));
} else if (match.Groups[3].Value != String.Empty) {
return Mal.types.Nil;
} else if (match.Groups[4].Value != String.Empty) {
@@ -75,7 +75,9 @@ namespace Mal {
.Replace("\\n", "\n");
return new Mal.types.MalString(str);
} else if (match.Groups[7].Value != String.Empty) {
- return new Mal.types.MalSymbol(match.Groups[7].Value);
+ return new Mal.types.MalString("\u029e" + match.Groups[7].Value);
+ } else if (match.Groups[8].Value != String.Empty) {
+ return new Mal.types.MalSymbol(match.Groups[8].Value);
} else {
throw new ParseError("unrecognized '" + match.Groups[0] + "'");
}
diff --git a/cs/step1_read_print.cs b/cs/step1_read_print.cs
index 1b427c8..43bf3c4 100644
--- a/cs/step1_read_print.cs
+++ b/cs/step1_read_print.cs
@@ -21,27 +21,26 @@ namespace Mal {
}
// repl
- static MalVal RE(string env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
-
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), "");
+
if (args.Length > 0 && args[0] == "--raw") {
Mal.readline.mode = Mal.readline.Mode.Raw;
}
+
+ // repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(null, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step2_eval.cs b/cs/step2_eval.cs
index 1a95a81..8c68336 100644
--- a/cs/step2_eval.cs
+++ b/cs/step2_eval.cs
@@ -5,14 +5,14 @@ using System.Collections.Generic;
using Mal;
using MalVal = Mal.types.MalVal;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
namespace Mal {
- class step1_eval {
+ class step2_eval {
// read
static MalVal READ(string str) {
return reader.read_str(str);
@@ -45,7 +45,7 @@ namespace Mal {
static MalVal EVAL(MalVal orig_ast, Dictionary<string, MalVal> env) {
MalVal a0;
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -59,7 +59,7 @@ namespace Mal {
+ Mal.printer._pr_str(a0,true) + "'");
}
var el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
return f.apply(el.rest());
}
@@ -70,43 +70,32 @@ namespace Mal {
}
// repl
- static MalVal RE(Dictionary<string, MalVal> env, string str) {
- return EVAL(READ(str), env);
- }
-
- static public MalFunction plus = new MalFunction(
- a => (MalInteger)a[0] + (MalInteger)a[1] );
- static public MalFunction minus = new MalFunction(
- a => (MalInteger)a[0] - (MalInteger)a[1] );
- static public MalFunction multiply = new MalFunction(
- a => (MalInteger)a[0] * (MalInteger)a[1] );
- static public MalFunction divide = new MalFunction(
- a => (MalInteger)a[0] / (MalInteger)a[1] );
-
static void Main(string[] args) {
- string prompt = "user> ";
-
var repl_env = new Dictionary<string, MalVal> {
- {"+", plus},
- {"-", minus},
- {"*", multiply},
- {"/", divide},
+ {"+", new MalFunc(a => (MalInt)a[0] + (MalInt)a[1]) },
+ {"-", new MalFunc(a => (MalInt)a[0] - (MalInt)a[1]) },
+ {"*", new MalFunc(a => (MalInt)a[0] * (MalInt)a[1]) },
+ {"/", new MalFunc(a => (MalInt)a[0] / (MalInt)a[1]) },
};
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
if (args.Length > 0 && args[0] == "--raw") {
Mal.readline.mode = Mal.readline.Mode.Raw;
}
+
+ // repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step3_env.cs b/cs/step3_env.cs
index 2be7992..6c4f2fe 100644
--- a/cs/step3_env.cs
+++ b/cs/step3_env.cs
@@ -5,11 +5,11 @@ using System.Collections.Generic;
using Mal;
using MalVal = Mal.types.MalVal;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
@@ -22,8 +22,7 @@ namespace Mal {
// eval
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -47,7 +46,7 @@ namespace Mal {
static MalVal EVAL(MalVal orig_ast, Env env) {
MalVal a0, a1, a2, res;
MalList el;
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -66,7 +65,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -77,12 +76,12 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
return EVAL(a2, let_env);
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
return f.apply(el.rest());
}
}
@@ -93,45 +92,35 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
- static public MalFunction plus = new MalFunction(
- a => (MalInteger)a[0] + (MalInteger)a[1] );
- static public MalFunction minus = new MalFunction(
- a => (MalInteger)a[0] - (MalInteger)a[1] );
- static public MalFunction multiply = new MalFunction(
- a => (MalInteger)a[0] * (MalInteger)a[1] );
- static public MalFunction divide = new MalFunction(
- a => (MalInteger)a[0] / (MalInteger)a[1] );
-
-
static void Main(string[] args) {
- string prompt = "user> ";
-
var repl_env = new Mal.env.Env(null);
- repl_env.set("+", plus);
- repl_env.set("-", minus);
- repl_env.set("*", multiply);
- repl_env.set("/", divide);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
+ repl_env.set(new MalSymbol("+"), new MalFunc(
+ a => (MalInt)a[0] + (MalInt)a[1]) );
+ repl_env.set(new MalSymbol("-"), new MalFunc(
+ a => (MalInt)a[0] - (MalInt)a[1]) );
+ repl_env.set(new MalSymbol("*"), new MalFunc(
+ a => (MalInt)a[0] * (MalInt)a[1]) );
+ repl_env.set(new MalSymbol("/"), new MalFunc(
+ a => (MalInt)a[0] / (MalInt)a[1]) );
if (args.Length > 0 && args[0] == "--raw") {
Mal.readline.mode = Mal.readline.Mode.Raw;
}
-
+
// repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step4_if_fn_do.cs b/cs/step4_if_fn_do.cs
index 659fc18..aefe226 100644
--- a/cs/step4_if_fn_do.cs
+++ b/cs/step4_if_fn_do.cs
@@ -5,11 +5,11 @@ using System.Collections.Generic;
using Mal;
using MalVal = Mal.types.MalVal;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
@@ -22,8 +22,7 @@ namespace Mal {
// eval
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -47,7 +46,7 @@ namespace Mal {
static MalVal EVAL(MalVal orig_ast, Env env) {
MalVal a0, a1, a2, a3, res;
MalList el;
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -65,7 +64,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -76,7 +75,7 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
return EVAL(a2, let_env);
case "do":
@@ -102,11 +101,11 @@ namespace Mal {
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
- return new MalFunction(
+ return new MalFunc(
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
return f.apply(el.rest());
}
}
@@ -117,38 +116,35 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
- var repl_env = new env.Env(null);
foreach (var entry in core.ns) {
- repl_env.set(entry.Key, entry.Value);
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
}
// core.mal: defined using the language itself
- RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE("(def! not (fn* (a) (if a false true)))");
if (args.Length > 0 && args[0] == "--raw") {
Mal.readline.mode = Mal.readline.Mode.Raw;
}
-
+
// repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step5_tco.cs b/cs/step5_tco.cs
index e243153..55d414a 100644
--- a/cs/step5_tco.cs
+++ b/cs/step5_tco.cs
@@ -5,11 +5,11 @@ using System.Collections.Generic;
using Mal;
using MalVal = Mal.types.MalVal;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
@@ -22,8 +22,7 @@ namespace Mal {
// eval
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -50,7 +49,7 @@ namespace Mal {
while (true) {
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -68,7 +67,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -79,7 +78,7 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -107,11 +106,11 @@ namespace Mal {
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
- return new MalFunction(a2f, env, a1f,
+ return new MalFunc(a2f, env, a1f,
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
MalVal fnast = f.getAst();
if (fnast != null) {
orig_ast = fnast;
@@ -131,38 +130,35 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
- var repl_env = new env.Env(null);
foreach (var entry in core.ns) {
- repl_env.set(entry.Key, entry.Value);
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
}
// core.mal: defined using the language itself
- RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE("(def! not (fn* (a) (if a false true)))");
if (args.Length > 0 && args[0] == "--raw") {
Mal.readline.mode = Mal.readline.Mode.Raw;
}
-
+
// repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step6_file.cs b/cs/step6_file.cs
index 7e3bf7e..a84e87c 100644
--- a/cs/step6_file.cs
+++ b/cs/step6_file.cs
@@ -6,11 +6,11 @@ using Mal;
using MalVal = Mal.types.MalVal;
using MalString = Mal.types.MalString;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
@@ -23,8 +23,7 @@ namespace Mal {
// eval
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -51,7 +50,7 @@ namespace Mal {
while (true) {
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -69,7 +68,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -80,7 +79,7 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -108,11 +107,11 @@ namespace Mal {
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
- return new MalFunction(a2f, env, a1f,
+ return new MalFunc(a2f, env, a1f,
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
MalVal fnast = f.getAst();
if (fnast != null) {
orig_ast = fnast;
@@ -132,51 +131,49 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
- var repl_env = new env.Env(null);
foreach (var entry in core.ns) {
- repl_env.set(entry.Key, entry.Value);
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
+ }
+ repl_env.set(new MalSymbol("eval"), new MalFunc(
+ a => EVAL(a[0], repl_env)));
+ int fileIdx = 1;
+ if (args.Length > 0 && args[0] == "--raw") {
+ Mal.readline.mode = Mal.readline.Mode.Raw;
+ fileIdx = 2;
}
- repl_env.set("eval", new MalFunction(a => EVAL(a[0], repl_env)));
MalList _argv = new MalList();
- for (int i=1; i < args.Length; i++) {
+ for (int i=fileIdx; i < args.Length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
- RE(repl_env, "(def! not (fn* (a) (if a false true)))");
- RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+ RE("(def! not (fn* (a) (if a false true)))");
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
- int fileIdx = 0;
- if (args.Length > 0 && args[0] == "--raw") {
- Mal.readline.mode = Mal.readline.Mode.Raw;
- fileIdx = 1;
- }
if (args.Length > fileIdx) {
- RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
+ RE("(load-file \"" + args[fileIdx] + "\")");
return;
}
-
+
// repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step7_quote.cs b/cs/step7_quote.cs
index 537c20c..7b03152 100644
--- a/cs/step7_quote.cs
+++ b/cs/step7_quote.cs
@@ -6,11 +6,11 @@ using Mal;
using MalVal = Mal.types.MalVal;
using MalString = Mal.types.MalString;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
@@ -50,8 +50,7 @@ namespace Mal {
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -78,7 +77,7 @@ namespace Mal {
while (true) {
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -96,7 +95,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -107,7 +106,7 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -140,11 +139,11 @@ namespace Mal {
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
- return new MalFunction(a2f, env, a1f,
+ return new MalFunc(a2f, env, a1f,
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
MalVal fnast = f.getAst();
if (fnast != null) {
orig_ast = fnast;
@@ -164,51 +163,49 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
- var repl_env = new env.Env(null);
foreach (var entry in core.ns) {
- repl_env.set(entry.Key, entry.Value);
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
+ }
+ repl_env.set(new MalSymbol("eval"), new MalFunc(
+ a => EVAL(a[0], repl_env)));
+ int fileIdx = 1;
+ if (args.Length > 0 && args[0] == "--raw") {
+ Mal.readline.mode = Mal.readline.Mode.Raw;
+ fileIdx = 2;
}
- repl_env.set("eval", new MalFunction(a => EVAL(a[0], repl_env)));
MalList _argv = new MalList();
- for (int i=1; i < args.Length; i++) {
+ for (int i=fileIdx; i < args.Length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
- RE(repl_env, "(def! not (fn* (a) (if a false true)))");
- RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+ RE("(def! not (fn* (a) (if a false true)))");
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
- int fileIdx = 0;
- if (args.Length > 0 && args[0] == "--raw") {
- Mal.readline.mode = Mal.readline.Mode.Raw;
- fileIdx = 1;
- }
if (args.Length > fileIdx) {
- RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
+ RE("(load-file \"" + args[fileIdx] + "\")");
return;
}
-
+
// repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step8_macros.cs b/cs/step8_macros.cs
index b06b1fb..cac1411 100644
--- a/cs/step8_macros.cs
+++ b/cs/step8_macros.cs
@@ -6,11 +6,11 @@ using Mal;
using MalVal = Mal.types.MalVal;
using MalString = Mal.types.MalString;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
@@ -52,10 +52,10 @@ namespace Mal {
if (ast is MalList) {
MalVal a0 = ((MalList)ast)[0];
if (a0 is MalSymbol &&
- env.find(((MalSymbol)a0).getName()) != null) {
- MalVal mac = env.get(((MalSymbol)a0).getName());
- if (mac is MalFunction &&
- ((MalFunction)mac).isMacro()) {
+ env.find((MalSymbol)a0) != null) {
+ MalVal mac = env.get((MalSymbol)a0);
+ if (mac is MalFunc &&
+ ((MalFunc)mac).isMacro()) {
return true;
}
}
@@ -66,7 +66,7 @@ namespace Mal {
public static MalVal macroexpand(MalVal ast, Env env) {
while (is_macro_call(ast, env)) {
MalSymbol a0 = (MalSymbol)((MalList)ast)[0];
- MalFunction mac = (MalFunction) env.get(a0.getName());
+ MalFunc mac = (MalFunc) env.get(a0);
ast = mac.apply(((MalList)ast).rest());
}
return ast;
@@ -74,8 +74,7 @@ namespace Mal {
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -102,7 +101,7 @@ namespace Mal {
while (true) {
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -123,7 +122,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -134,7 +133,7 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -148,8 +147,8 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- ((MalFunction)res).setMacro();
- env.set(((MalSymbol)a1).getName(), res);
+ ((MalFunc)res).setMacro();
+ env.set(((MalSymbol)a1), res);
return res;
case "macroexpand":
a1 = ast[1];
@@ -177,11 +176,11 @@ namespace Mal {
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
- return new MalFunction(a2f, env, a1f,
+ return new MalFunc(a2f, env, a1f,
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
MalVal fnast = f.getAst();
if (fnast != null) {
orig_ast = fnast;
@@ -201,53 +200,51 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
- var repl_env = new env.Env(null);
foreach (var entry in core.ns) {
- repl_env.set(entry.Key, entry.Value);
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
+ }
+ repl_env.set(new MalSymbol("eval"), new MalFunc(
+ a => EVAL(a[0], repl_env)));
+ int fileIdx = 1;
+ if (args.Length > 0 && args[0] == "--raw") {
+ Mal.readline.mode = Mal.readline.Mode.Raw;
+ fileIdx = 2;
}
- repl_env.set("eval", new MalFunction(a => EVAL(a[0], repl_env)));
MalList _argv = new MalList();
- for (int i=1; i < args.Length; i++) {
+ for (int i=fileIdx; i < args.Length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
- RE(repl_env, "(def! not (fn* (a) (if a false true)))");
- RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
- RE(repl_env, "(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)))))))");
- RE(repl_env, "(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))))))))");
+ RE("(def! not (fn* (a) (if a false true)))");
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+ RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
+ RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
- int fileIdx = 0;
- if (args.Length > 0 && args[0] == "--raw") {
- Mal.readline.mode = Mal.readline.Mode.Raw;
- fileIdx = 1;
- }
if (args.Length > fileIdx) {
- RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
+ RE("(load-file \"" + args[fileIdx] + "\")");
return;
}
-
+
// repl loop
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
diff --git a/cs/step9_try.cs b/cs/step9_try.cs
new file mode 100644
index 0000000..bea360c
--- /dev/null
+++ b/cs/step9_try.cs
@@ -0,0 +1,283 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using Mal;
+using MalVal = Mal.types.MalVal;
+using MalString = Mal.types.MalString;
+using MalSymbol = Mal.types.MalSymbol;
+using MalInt = Mal.types.MalInt;
+using MalList = Mal.types.MalList;
+using MalVector = Mal.types.MalVector;
+using MalHashMap = Mal.types.MalHashMap;
+using MalFunc = Mal.types.MalFunc;
+using Env = Mal.env.Env;
+
+namespace Mal {
+ class step9_try {
+ // read
+ static MalVal READ(string str) {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static bool is_pair(MalVal x) {
+ return x is MalList && ((MalList)x).size() > 0;
+ }
+
+ public static MalVal quasiquote(MalVal ast) {
+ if (!is_pair(ast)) {
+ return new MalList(new MalSymbol("quote"), ast);
+ } else {
+ MalVal a0 = ((MalList)ast)[0];
+ if ((a0 is MalSymbol) &&
+ (((MalSymbol)a0).getName() == "unquote")) {
+ return ((MalList)ast)[1];
+ } else if (is_pair(a0)) {
+ MalVal a00 = ((MalList)a0)[0];
+ if ((a00 is MalSymbol) &&
+ (((MalSymbol)a00).getName() == "splice-unquote")) {
+ return new MalList(new MalSymbol("concat"),
+ ((MalList)a0)[1],
+ quasiquote(((MalList)ast).rest()));
+ }
+ }
+ return new MalList(new MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(((MalList)ast).rest()));
+ }
+ }
+
+ public static bool is_macro_call(MalVal ast, Env env) {
+ if (ast is MalList) {
+ MalVal a0 = ((MalList)ast)[0];
+ if (a0 is MalSymbol &&
+ env.find((MalSymbol)a0) != null) {
+ MalVal mac = env.get((MalSymbol)a0);
+ if (mac is MalFunc &&
+ ((MalFunc)mac).isMacro()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static MalVal macroexpand(MalVal ast, Env env) {
+ while (is_macro_call(ast, env)) {
+ MalSymbol a0 = (MalSymbol)((MalList)ast)[0];
+ MalFunc mac = (MalFunc) env.get(a0);
+ ast = mac.apply(((MalList)ast).rest());
+ }
+ return ast;
+ }
+
+ static MalVal eval_ast(MalVal ast, Env env) {
+ if (ast is MalSymbol) {
+ return env.get((MalSymbol)ast);
+ } else if (ast is MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = ast.list_Q() ? new MalList()
+ : (MalList)new MalVector();
+ foreach (MalVal mv in old_lst.getValue()) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast is MalHashMap) {
+ var new_dict = new Dictionary<string, MalVal>();
+ foreach (var entry in ((MalHashMap)ast).getValue()) {
+ new_dict.Add(entry.Key, EVAL((MalVal)entry.Value, env));
+ }
+ return new MalHashMap(new_dict);
+ } else {
+ return ast;
+ }
+ }
+
+
+ static MalVal EVAL(MalVal orig_ast, Env env) {
+ MalVal a0, a1, a2, res;
+ MalList el;
+
+ while (true) {
+
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
+ if (!orig_ast.list_Q()) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalVal expanded = macroexpand(orig_ast, env);
+ if (!expanded.list_Q()) { return expanded; }
+ MalList ast = (MalList) expanded;
+
+ if (ast.size() == 0) { return ast; }
+ a0 = ast[0];
+
+ String a0sym = a0 is MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+
+ switch (a0sym) {
+ case "def!":
+ a1 = ast[1];
+ a2 = ast[2];
+ res = EVAL(a2, env);
+ env.set((MalSymbol)a1, res);
+ return res;
+ case "let*":
+ a1 = ast[1];
+ a2 = ast[2];
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1)[i];
+ val = ((MalList)a1)[i+1];
+ let_env.set(key, EVAL(val, let_env));
+ }
+ orig_ast = a2;
+ env = let_env;
+ break;
+ case "quote":
+ return ast[1];
+ case "quasiquote":
+ orig_ast = quasiquote(ast[1]);
+ break;
+ case "defmacro!":
+ a1 = ast[1];
+ a2 = ast[2];
+ res = EVAL(a2, env);
+ ((MalFunc)res).setMacro();
+ env.set(((MalSymbol)a1), res);
+ return res;
+ case "macroexpand":
+ a1 = ast[1];
+ return macroexpand(a1, env);
+ case "try*":
+ try {
+ return EVAL(ast[1], env);
+ } catch (Exception e) {
+ if (ast.size() > 2) {
+ MalVal exc;
+ a2 = ast[2];
+ MalVal a20 = ((MalList)a2)[0];
+ if (((MalSymbol)a20).getName() == "catch*") {
+ if (e is Mal.types.MalException) {
+ exc = ((Mal.types.MalException)e).getValue();
+ } else {
+ exc = new MalString(e.StackTrace);
+ }
+ return EVAL(((MalList)a2)[2],
+ new Env(env, ((MalList)a2).slice(1,2),
+ new MalList(exc)));
+ }
+ }
+ throw e;
+ }
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast[ast.size()-1];
+ break;
+ case "if":
+ a1 = ast[1];
+ MalVal cond = EVAL(a1, env);
+ if (cond == Mal.types.Nil || cond == Mal.types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast[3];
+ } else {
+ return Mal.types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast[2];
+ }
+ break;
+ case "fn*":
+ MalList a1f = (MalList)ast[1];
+ MalVal a2f = ast[2];
+ Env cur_env = env;
+ return new MalFunc(a2f, env, a1f,
+ args => EVAL(a2f, new Env(cur_env, a1f, args)) );
+ default:
+ el = (MalList)eval_ast(ast, env);
+ var f = (MalFunc)el[0];
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = f.genEnv(el.rest());
+ } else {
+ return f.apply(el.rest());
+ }
+ break;
+ }
+
+ }
+ }
+
+ // print
+ static string PRINT(MalVal exp) {
+ return printer._pr_str(exp, true);
+ }
+
+ // repl
+ static void Main(string[] args) {
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
+
+ // core.cs: defined using C#
+ foreach (var entry in core.ns) {
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
+ }
+ repl_env.set(new MalSymbol("eval"), new MalFunc(
+ a => EVAL(a[0], repl_env)));
+ int fileIdx = 1;
+ if (args.Length > 0 && args[0] == "--raw") {
+ Mal.readline.mode = Mal.readline.Mode.Raw;
+ fileIdx = 2;
+ }
+ MalList _argv = new MalList();
+ for (int i=fileIdx; i < args.Length; i++) {
+ _argv.conj_BANG(new MalString(args[i]));
+ }
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
+
+ // core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))");
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+ RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
+ RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
+
+ if (args.Length > fileIdx) {
+ RE("(load-file \"" + args[fileIdx] + "\")");
+ return;
+ }
+
+ // repl loop
+ while (true) {
+ string line;
+ try {
+ line = Mal.readline.Readline("user> ");
+ if (line == null) { break; }
+ if (line == "") { continue; }
+ } catch (IOException e) {
+ Console.WriteLine("IOException: " + e.Message);
+ break;
+ }
+ try {
+ Console.WriteLine(PRINT(RE(line)));
+ } catch (Mal.types.MalContinue) {
+ continue;
+ } catch (Mal.types.MalException e) {
+ Console.WriteLine("Error: " +
+ printer._pr_str(e.getValue(), false));
+ continue;
+ } catch (Exception e) {
+ Console.WriteLine("Error: " + e.Message);
+ Console.WriteLine(e.StackTrace);
+ continue;
+ }
+ }
+ }
+ }
+}
diff --git a/cs/stepA_more.cs b/cs/stepA_mal.cs
index 486c344..0ccb39e 100644
--- a/cs/stepA_more.cs
+++ b/cs/stepA_mal.cs
@@ -6,15 +6,15 @@ using Mal;
using MalVal = Mal.types.MalVal;
using MalString = Mal.types.MalString;
using MalSymbol = Mal.types.MalSymbol;
-using MalInteger = Mal.types.MalInteger;
+using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
-using MalFunction = Mal.types.MalFunction;
+using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
- class stepA_more {
+ class stepA_mal {
// read
static MalVal READ(string str) {
return reader.read_str(str);
@@ -52,10 +52,10 @@ namespace Mal {
if (ast is MalList) {
MalVal a0 = ((MalList)ast)[0];
if (a0 is MalSymbol &&
- env.find(((MalSymbol)a0).getName()) != null) {
- MalVal mac = env.get(((MalSymbol)a0).getName());
- if (mac is MalFunction &&
- ((MalFunction)mac).isMacro()) {
+ env.find((MalSymbol)a0) != null) {
+ MalVal mac = env.get((MalSymbol)a0);
+ if (mac is MalFunc &&
+ ((MalFunc)mac).isMacro()) {
return true;
}
}
@@ -66,7 +66,7 @@ namespace Mal {
public static MalVal macroexpand(MalVal ast, Env env) {
while (is_macro_call(ast, env)) {
MalSymbol a0 = (MalSymbol)((MalList)ast)[0];
- MalFunction mac = (MalFunction) env.get(a0.getName());
+ MalFunc mac = (MalFunc) env.get(a0);
ast = mac.apply(((MalList)ast).rest());
}
return ast;
@@ -74,8 +74,7 @@ namespace Mal {
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -102,7 +101,7 @@ namespace Mal {
while (true) {
- //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
@@ -123,7 +122,7 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
@@ -134,7 +133,7 @@ namespace Mal {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -148,8 +147,8 @@ namespace Mal {
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
- ((MalFunction)res).setMacro();
- env.set(((MalSymbol)a1).getName(), res);
+ ((MalFunc)res).setMacro();
+ env.set(((MalSymbol)a1), res);
return res;
case "macroexpand":
a1 = ast[1];
@@ -198,11 +197,11 @@ namespace Mal {
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
- return new MalFunction(a2f, env, a1f,
+ return new MalFunc(a2f, env, a1f,
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
- var f = (MalFunction)el[0];
+ var f = (MalFunc)el[0];
MalVal fnast = f.getAst();
if (fnast != null) {
orig_ast = fnast;
@@ -222,55 +221,53 @@ namespace Mal {
}
// repl
- static MalVal RE(Env env, string str) {
- return EVAL(READ(str), env);
- }
-
static void Main(string[] args) {
- string prompt = "user> ";
+ var repl_env = new Mal.env.Env(null);
+ Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
- var repl_env = new env.Env(null);
foreach (var entry in core.ns) {
- repl_env.set(entry.Key, entry.Value);
+ repl_env.set(new MalSymbol(entry.Key), entry.Value);
+ }
+ repl_env.set(new MalSymbol("eval"), new MalFunc(
+ a => EVAL(a[0], repl_env)));
+ int fileIdx = 0;
+ if (args.Length > 0 && args[0] == "--raw") {
+ Mal.readline.mode = Mal.readline.Mode.Raw;
+ fileIdx = 1;
}
- repl_env.set("eval", new MalFunction(a => EVAL(a[0], repl_env)));
MalList _argv = new MalList();
- for (int i=1; i < args.Length; i++) {
+ for (int i=fileIdx; i < args.Length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
- RE(repl_env, "(def! *host-language* \"c#\")");
- RE(repl_env, "(def! not (fn* (a) (if a false true)))");
- RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
- RE(repl_env, "(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)))))))");
- RE(repl_env, "(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))))))))");
+ RE("(def! *host-language* \"c#\")");
+ RE("(def! not (fn* (a) (if a false true)))");
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+ RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
+ RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
- int fileIdx = 0;
- if (args.Length > 0 && args[0] == "--raw") {
- Mal.readline.mode = Mal.readline.Mode.Raw;
- fileIdx = 1;
- }
if (args.Length > fileIdx) {
- RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
+ RE("(load-file \"" + args[fileIdx] + "\")");
return;
}
-
+
// repl loop
- RE(repl_env, "(println (str \"Mal [\" *host-language* \"]\"))");
+ RE("(println (str \"Mal [\" *host-language* \"]\"))");
while (true) {
string line;
try {
- line = Mal.readline.Readline(prompt);
+ line = Mal.readline.Readline("user> ");
if (line == null) { break; }
+ if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
- Console.WriteLine(PRINT(RE(repl_env, line)));
+ Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Mal.types.MalException e) {
diff --git a/cs/types.cs b/cs/types.cs
index b49adb6..b96a9f4 100644
--- a/cs/types.cs
+++ b/cs/types.cs
@@ -39,9 +39,9 @@ namespace Mal {
(a is MalList && b is MalList))) {
return false;
} else {
- if (a is MalInteger) {
- return ((MalInteger)a).getValue() ==
- ((MalInteger)b).getValue();
+ if (a is MalInt) {
+ return ((MalInt)a).getValue() ==
+ ((MalInt)b).getValue();
} else if (a is MalSymbol) {
return ((MalSymbol)a).getName() ==
((MalSymbol)b).getName();
@@ -97,47 +97,48 @@ namespace Mal {
static public MalConstant True = new MalConstant("true");
static public MalConstant False = new MalConstant("false");
- public class MalInteger : MalVal {
- int value;
- public MalInteger(int v) { value = v; }
- public new MalInteger copy() { return this; }
+ public class MalInt : MalVal {
+ Int64 value;
+ public MalInt(Int64 v) { value = v; }
+ public new MalInt copy() { return this; }
- public int getValue() { return value; }
+ public Int64 getValue() { return value; }
public override string ToString() {
return value.ToString();
}
public override string ToString(bool print_readably) {
return value.ToString();
}
- public static MalConstant operator <(MalInteger a, MalInteger b) {
+ public static MalConstant operator <(MalInt a, MalInt b) {
return a.getValue() < b.getValue() ? True : False;
}
- public static MalConstant operator <=(MalInteger a, MalInteger b) {
+ public static MalConstant operator <=(MalInt a, MalInt b) {
return a.getValue() <= b.getValue() ? True : False;
}
- public static MalConstant operator >(MalInteger a, MalInteger b) {
+ public static MalConstant operator >(MalInt a, MalInt b) {
return a.getValue() > b.getValue() ? True : False;
}
- public static MalConstant operator >=(MalInteger a, MalInteger b) {
+ public static MalConstant operator >=(MalInt a, MalInt b) {
return a.getValue() >= b.getValue() ? True : False;
}
- public static MalInteger operator +(MalInteger a, MalInteger b) {
- return new MalInteger(a.getValue() + b.getValue());
+ public static MalInt operator +(MalInt a, MalInt b) {
+ return new MalInt(a.getValue() + b.getValue());
}
- public static MalInteger operator -(MalInteger a, MalInteger b) {
- return new MalInteger(a.getValue() - b.getValue());
+ public static MalInt operator -(MalInt a, MalInt b) {
+ return new MalInt(a.getValue() - b.getValue());
}
- public static MalInteger operator *(MalInteger a, MalInteger b) {
- return new MalInteger(a.getValue() * b.getValue());
+ public static MalInt operator *(MalInt a, MalInt b) {
+ return new MalInt(a.getValue() * b.getValue());
}
- public static MalInteger operator /(MalInteger a, MalInteger b) {
- return new MalInteger(a.getValue() / b.getValue());
+ public static MalInt operator /(MalInt a, MalInt b) {
+ return new MalInt(a.getValue() / b.getValue());
}
}
public class MalSymbol : MalVal {
string value;
public MalSymbol(string v) { value = v; }
+ public MalSymbol(MalString v) { value = v.getValue(); }
public new MalSymbol copy() { return this; }
public string getName() { return value; }
@@ -159,7 +160,9 @@ namespace Mal {
return "\"" + value + "\"";
}
public override string ToString(bool print_readably) {
- if (print_readably) {
+ if (value.Length > 0 && value[0] == '\u029e') {
+ return ":" + value.Substring(1);
+ } else if (print_readably) {
return "\"" + value.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n") + "\"";
@@ -204,10 +207,10 @@ namespace Mal {
public int size() { return value.Count; }
public MalVal nth(int idx) {
- return value.Count > 0 ? value[idx] : Nil;
+ return value.Count > idx ? value[idx] : Nil;
}
public MalVal this[int idx] {
- get { return value.Count > 0 ? value[idx] : Nil; }
+ get { return value.Count > idx ? value[idx] : Nil; }
}
public MalList rest() {
if (size() > 0) {
@@ -298,16 +301,16 @@ namespace Mal {
}
}
- public class MalFunction : MalVal {
+ public class MalFunc : MalVal {
Func<MalList, MalVal> fn = null;
MalVal ast = null;
Mal.env.Env env = null;
MalList fparams;
bool macro = false;
- public MalFunction(Func<MalList, MalVal> fn) {
+ public MalFunc(Func<MalList, MalVal> fn) {
this.fn = fn;
}
- public MalFunction(MalVal ast, Mal.env.Env env, MalList fparams,
+ public MalFunc(MalVal ast, Mal.env.Env env, MalList fparams,
Func<MalList, MalVal> fn) {
this.fn = fn;
this.ast = ast;
diff --git a/docs/TODO b/docs/TODO
index 0d5d28b..68c5393 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -1,18 +1,50 @@
All:
- - multi-line read
- - loop/recur ?
- - hash-maps with non-string keys
- - hash-map with space in key string (make)
- - keyword type
- - gensym reader inside quasiquote
-
- - per impl tests for step5_tco, step9_interop (if possible)
+ - Finish guide.md
+
+ - test to check args set properly
+ - test to make sure slurp captures final newline
+ - make sure errors propagate/print properly when self-hosted
+
+ * change perf test to run for 10 seconds and then calculate number
+ of iterations per second
+ - redefine (defmacro!) as (def! (macro*))
+ - runtest expect fixes:
+ * stop using expect, so we can drop --raw option
+ * fix C#, VB
+ - fix long line splitting in runtest
- regular expression matching in runtest
+ - add re (use in rep) everywhere and use that (to avoid printing)
+ - Implement/fix interop
- Print full exception when test gets EOF from expect
+ - metadata on symbols
+ - metadata as a map only. ^ merges metadata in the reader itself.
+ Line numbers in metadata from reader.
+ - protocols!
+ - https://github.com/pixie-lang/pixie
+ - http://www.toccata.io/2015/01/Mapping/
+ - namespaces
+ - environments first class: *ENV*, *outer* defined by env-new
+ - namespaces is *namespaces* map in environment which maps namespace
+ names to other environments.
+ - def! become an alias for (env-set! *ENV* 'sym value)
+ - Namespace lookup: go up the environment hierarchy until
+ a *namespaces* map is found with the namespace name being
+ looked up. Then the symbol would be looked up starting in
+ the namespace environment. Need protocols first probably.
- Break out impl eval into step0.5
- Fix quasiquoting of vectors
+ - Get self-host working at earlier step:
+ - Move try* to step6
+ - Remove macros from mal
+
+ - multi-line REPL read
+ - loop/recur ?
+ - gensym reader inside quasiquote
+
+ - per impl tests for step5_tco
+
---------------------------------------------
Bash:
@@ -21,112 +53,139 @@ Bash:
C:
- come up with better way to do 20 vararg code
- - GC
+ - GC: use http://www.hboehm.info/gc/
+ - fix mal/clojurewest2014.mal
C#:
- - step9_interop
+ - fix command line arg processing (doesn't run file specified)
+ - accumulates line breaks with mal/clojurewest2014.mal
+ - step9_interop:
+ http://www.ckode.dk/programming/eval-in-c-yes-its-possible/
Clojure:
+ - make indent consistent across steps (e.g. step5, step8)
+ - fix mal/clojurewest2014.mal
+
+CoffeeScript:
+ - make target to compile to JS
+ - fix "user> " prompt with mal/clojurewest2014.mal
+
+Go:
+ - consider variable arguments in places where it makes sense
+ https://gobyexample.com/variadic-functions
+
+Haskell:
+ - TCO using seq/bang patterns:
+ http://stackoverflow.com/questions/9149183/tail-optimization-guarantee-loop-encoding-in-haskell
+ - immediately exits mal/clojurewest2014.mal
Java:
- - step9_interop
+ - Use gradle instead of mvn
+ http://blog.paralleluniverse.co/2014/05/01/modern-java/
+ - MAL formatting is a bit off with mal/clojurewest2014.mal
Javascript:
+ - interop: adopt techniques from miniMAL
+ - fix "user> " prompt with mal/clojurewest2014.mal
+
+Lua:
+ - time-ms should get actual milliseconds
Make:
- allow '_' in make variable names
+ - hash-map with space in key string
+ - Fix: make -f stepA_mal.mk ../mal/step6_file.mal
+ (slurp "../tests/incA.mal")
+ (read-string "(+ 2 3)")
- errors should propagate up from within load-file
+ - GC: expore using "undefined" directive in Make 3.82
Mal:
- line numbers in errors
- step5_tco
- - step9_interop
+
+miniMAL:
+ - figure out why {} literals are "static"/persistent
Perl:
+ - fix metadata on native functions
+ - implement conj
PHP:
+ - formatting messed up with mal/clojurewest2014.mal
Postscript:
- - negative numbers
+ - add negative numbers
+ - fix blank line after comments
+ - fix command line arg processing (doesn't run file specified)
+ - formatting messed up with mal/clojurewest2014.mal
Python:
- - error: python ../python/stepA_more.py ../mal/stepA_more.mal ../mal/stepA_more.mal
+ - error: python ../python/stepA_mal.py ../mal/stepA_mal.mal ../mal/stepA_mal.mal
+ - interop tests
+
+R:
+ - tracebacks in errors
+ - fix running from different directory
+ - formatting messed up with mal/clojurewest2014.mal
+
+Racket
+ - metadata on collections
Ruby:
+Rust:
+ - use built-in regex once fixed:
+ https://github.com/rust-lang/rust/issues/18034
+ https://github.com/rust-lang/rust/issues/18035
+ - fix 'make all' invocation of cargo build
+ - formatting messed up with mal/clojurewest2014.mal
+
+Scala
+ - readline
+ - fix exception when finished running something on command line
+
+VB.Net
+ - convert readline.cs to readline.vb
+
---------------------------------------------
Future Implementations:
- - Rust:
- - http://www.rustforrubyists.com/book/index.html
- - http://static.rust-lang.org/doc/0.9/complement-cheatsheet.html
- - http://pzol.github.io/getting_rusty/
- - release notes:
- - https://github.com/mozilla/rust/wiki/Doc-detailed-release-notes
- - this week in rust:
- - http://cmr.github.io/
- - readline:
- - http://redbrain.co.uk/2013/11/09/rust-and-readline-c-ffi/
- - http://www.reddit.com/r/rust/comments/1q9pqc/rust_cffi_and_readline/
- - https://github.com/dbp/rustrepl
- - hash-map:
- - http://static.rust-lang.org/doc/master/std/hashmap/index.html
- - http://static.rust-lang.org/doc/master/std/hashmap/struct.HashMap.html
- - vector/list:
- - http://static.rust-lang.org/doc/master/std/vec/index.html
- - example code:
- - https://github.com/dradtke/rust-dominion/blob/master/dominion/mod.rs
-
- - Redmonk languages from Jan 2014:
- http://sogrady-media.redmonk.com/sogrady/files/2014/01/lang-rank-114-wm.png
-
- - Tier 1
- * JavaScript
- * Java
- * PHP
- * C#
- * Python
- - C++
- * Ruby
- * C
- - Objective-C
- * Perl
- * Shell (Bash 4)
-
- - Tier 2
- - Scala
- - Haskell
- * Clojure
- - CoffeeScript
- - Visual Basic
- - Groovy
- - Go
- - Lua
- - Erlang
- - Emacs Lisp
- - Assembly
- - Scheme
- - FORTRAN
- - Dart
- - F#
- - D
-
- - Tier 3
- - TypeScript
- - Racket
- - HaXe
- - Pascal
- - VimL
- - https://github.com/tpope/timl
- - Common Lisp
- - Rust
- - M (OpenM/MUMPS)
- - Factor (Stack-based)
-
- - Others:
- - Forth (Stack-based)
- - BF (Crazy)
+ - Ada (gnat)
+ - http://rosettacode.org/wiki/Regular_expressions#Ada
+
+ - C++
+
+ - Groovy
+ - http://groovy-lang.org/learn.html
+ - http://groovy-lang.org/structure.html
+
+ - Erlang
+
+ - F#
+
+ - Haxe
+ - http://api.haxe.org/
+ - http://haxe.us/haxe_tutorial.html
+
+ - Julia
+
+ - Nim
+
+ - Objective-C:
+
+ - Pascal:
+ sudo aptitude install fp-compiler-2.6.2
+
+ - VimL
+ - https://github.com/tpope/timl
+
+ - Tcl
+ - TeX/LaTeX
+ - Basic interpreter in TeX: http://ctanhg.scharrer-online.de/pkg/basix.html
+ - Cheat Sheet: http://www.stdout.org/~winston/latex/latexsheet.pd
+ - latex '\nonstopmode\input' blah.tex
diff --git a/docs/step_notes.txt b/docs/step_notes.txt
index 09bc356..e28761a 100644
--- a/docs/step_notes.txt
+++ b/docs/step_notes.txt
@@ -4,6 +4,19 @@ Step Notes:
- prompt, input, READ, EVAL, PRINT, output
- readline module
- display prompt, read line of input
+ - Details:
+ - get your language compiler/interpreter running
+ - create step0_repl.EXT
+ - loop that reads input, calls rep, writes output, exits
+ on EOF/Ctrl-D
+ - rep calls PRINT(EVAL(READ(str)))
+ - READ, EVAL, PRINT just return input parameter
+ - modify toplevel Makefile
+ - add language (directory name) to IMPLS
+ - add <lang>_STEP_TO_PROG entry
+ - add <lang>_RUNSTEP entry
+ - for a compiled language, add <lang>/Makefile
+ - targets: all, step*, stats, stats-lisp,
- use native eval in EVAL if available
@@ -17,6 +30,7 @@ Step Notes:
- types module:
- add boxed types if no language equivalent:
- nil, true, false, symbol, integer, string, list
+ - error types if necessary
- reader module:
- stateful reader object
- alternative: mutate token list
@@ -33,15 +47,42 @@ Step Notes:
- read_atom (not atom type)
- return scalar boxed type:
- nil, true, false, symbol, integer, string
+ - skip unquoting
- printer module:
- _pr_str:
- stringify boxed types to their Mal representations
- list/array is recursive
+ - skip quoting
- repl loop
- catch errors, print them and continue
- impls without exception handling will need to have a global
variable with checks for it at the beginning of critical
code sections
+ - Details:
+ - copy step0_repl.EXT to step1_read_print.EXT
+ - modify Makefile if compiled
+ - call reader.read_str from READ
+ - pass through type returned from read_str through
+ READ/EVAL/PRINT
+ - create reader.EXT
+ - if regex support (much easier) then tokenize with this:
+ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g
+ - add read_str:
+ - call tokenize
+ - handle blank line (exceptions, return code, global
+ depending on lang features)
+ - read_str -> read_form -> {read_list, read_atom}
+ - mutable reader thing
+ - create printer.EXT
+ - _pr_str function which basically reverses read_str and
+ returns a string representation
+ - run `make test^EXT^step1`. Much of the basics should pass up
+ to vectors
+ - implement read_hash_map (can refactor read_list)
+ - import read_vector
+ - probably want to define types for List and Vector in
+ types.EXT that extend or wrap native arrays
+ - run `make test^EXT^step1`. All mandatory should pass
- comments
@@ -82,8 +123,23 @@ Step Notes:
- EVAL/apply:
- if not a list, call eval_ast
- otherwise, apply first item to eval_ast of (rest ast)
- - repl_env as simple one level assoc. array (or hash_map)
+ - repl_env as simple one level hash map (assoc. array)
- store function as hash_map value
+ - Details:
+ - copy step1_read_print.EXT to step2_eval.EXT
+ - create repl_env hash_map) with +, -, *, /
+ - store anon func as values if possible
+ - types.EXT
+ - implement symbol? (symbol_Q) and list? (list_Q)
+ - add env param to EVAL and add to rep EVAL call
+ - EVAL
+ - if not list call eval_ast
+ - otherwise eval_ast, and call first arg with rest
+ - eval_ast
+ - if symbol?, lookup in env
+ - if List, EVAL each and return eval's list
+ - otherwise, return original
+ - optional: handle vector and hash-map in eval_ast
- vectors
- eval each item, return new vector
@@ -99,6 +155,22 @@ Step Notes:
- EVAL/apply:
- def! - mutate current environment
- let* - create new environment with bindings
+ - Details:
+ - cp step2_eval.EXT to step3_env.EXT
+ - add env.EXT if lang support file dep cycles, otherwise, add
+ to types.EXT
+ - Env type
+ - find, get, set methods/functions
+ - use Env type instead of map/assoc. array
+ - eval_ast: use method for lookup
+ - EVAL:
+ - switch on first symbol
+ - def!
+ - set env[a1] to EVAL(a2, env)
+ - let*
+ - loop through let building up let_env
+ - EVAL(a2, let_env)
+ - move apply to default
- step4_if_fn_do
- types module:
@@ -126,13 +198,45 @@ Step Notes:
- otherwise needs a way of representing functions that can
have associated metadata
- define "not" using REP/RE
+ - Details:
+ - cp step3_env.EXT to step4_env.EXT
+ - modify Makefile if compiled
+ - env.EXT
+ - add binds and exprs args. Create new environments with
+ exprs bound to binds. If & symbol, bind rest of exprs to
+ next bind symbol
+ - EVAL:
+ - do:
+ - eval_ast [1:], then return last eval'd element
+ - if
+ - EVAL(a1)
+ - if true EVAL(a2)
+ - else EVAL(a3), unless no a3 then return nil
+ - fn*
+ - if available use function closures to return a new
+ native function that calls EVAL(a2, Env(env, a1, fargs))
+ - otherwise, store exp, params and env in a structure
+ - core.EXT
+ - create ns object to hold core namespace
+ - move numeric operators here
+ - add comparison operators
+ - add list, list?, empty?, count
+ - run make test^EXT^step4
+ - implement equal?/equal_Q in types.EXT and refer in core.ns
+ - implement not as rep("(def! not (fn* (a) (if a false true)))")
+ - run make test^EXT^step4: should pass everything except
+ string routines
+ - implement: pr-str, str, prn, println in core.EXT and
+ refer in core.ns
+ - should leverage pr-str from printer.EXT
+ - add reader/printer string quote/unquote
- step5_tco
- types module:
- mal function type:
- - stores: func, exp, env, params
- - func is EVAL in native mal case, otherwise reference to
- platform function
+ - stores: eval, exp, env, params
+ - eval is EVAL in native mal case (needed for map function
+ later), otherwise reference to platform function
- if metadata support, then store exp, env, params as
metadata
- printer
@@ -142,8 +246,24 @@ Step Notes:
- cases where we directly return result of EVAL, instead set
ast and env to what would be put in the EVAL, then loop.
- do, if, "apply"
- - for "apply" case, set env to new Env based on properties
- on the function
+ - "apply"
+ - if mal function type
+ - set env to new Env based on properties on the function
+ - if native function, same as before
+ - Details:
+ - types.EXT
+ - create Mal function type to store eval, exp, env, params
+ - cp step4_if_fn_do.EXT to step5_tco.EXT
+ - wrap EVAL in infinite while loop
+ - in let*, do, and if:
+ - set ast and env and loop (no return)
+ - in fn* create Mal function type
+ - if compiled, update Makefile
+ - in apply, test if Mal function type:
+ - if so, generate new env from stored env, args and callee
+ params
+ - set ast to stored ast
+
- step6_file
- core module:
@@ -152,6 +272,26 @@ Step Notes:
- set *ARGV*
- if files on command line, use load-file to run first argument
using rest as arguments
+ - Details:
+ - cp step5_tco.EXT to step6_file.EXT
+ - if compiled update Makefile
+ - add eval to repl_env
+ - if no (or limited closures) may have to add an "eval"
+ case to EVAL and use function which gets root of
+ environment to env.EXT (see rust).
+ - add empty *ARGV* list to repl_env
+ - in core.ns:
+ - wrap printer.read-str as read-string
+ - implement slurp
+ - implement load-file using rep
+ - test:
+ (load-file "../tests/inc.mal")
+ (inc3 10)
+ - implement command line execution
+ - test:
+ ./step6_file ../tests/incA.mal
+ =>9
+ - implement comments in reader.EXT (ignore in tokenize)
- step7_quote
- add is_pair and quasiquote functions
@@ -164,6 +304,15 @@ Step Notes:
- reader module:
- add reader macros to read_form for quote, unquote,
splice-unquote and quasiquote
+ - Details:
+ - cp step6_file.EXT to step6_quote.EXT
+ - if compiled update Makefile
+ - implement reader macros (', `, ~, ~@) in reader
+ - retest make test^go^step1
+ - add is_pair and quasiquote
+ - add quote and quasiquote cases to EVAL
+ - implement cons and concat in core.EXT
+ - retest test^go^step7
- step8_macros
- types
@@ -178,12 +327,19 @@ Step Notes:
- EVAL:
- add 'defmacro!' and 'macroexpand'
- set ismacro property on function
+ - Details:
+ - cp step7_quote.EXT to step8_macros.EXT
+ - if compiled update Makefile
+ - add isMacro property to Mal Function type
+ - may need to go back and adjust step5-7
+ - implement is_macro_call and macroexpand
+ - call macroexpand on ast before apply in EVAL
+ - add defmacro! and macroexpand to EVAL switch
+ - make test^go^step8 should pass some basic macros
+ - add nth, first, and rest to core.ns
+ - make test^go^step8 should now pass
-- step9_interop
- - convert returned data to mal data
- - recursive, similar to pr_str
-
-- stepA_more
+- step9_try
- core module:
- throw function
- apply, map functions: should not directly call EVAL, which
@@ -195,6 +351,33 @@ Step Notes:
otherwise extracts full value
- set and print *host-language*
- define cond and or macros using REP/RE
+ - Details:
+ - cp step8_macros.EXT to stepA_try.EXT
+ - if compiled update Makefile
+ - core.ns implement nil?, true?, false?, symbol?, sequential?,
+ vector, vector?
+ - add mal error type which wraps normal mal type
+ - in core.ns add throw which wraps type in mal error type
+ and throws/raises/sets exception
+ - add try*/catch* support to EVAL
+ - if mal error type, bind to catch* bind symbol
+ - otherwise, bind string of error to catch* bind symbol
+ - implement apply, map in core.ns
+ - make test^go^stepA
+ - implement readline.EXT
+ - provide option (e.g. commented out) to link with GNU
+ readline (GPL) or libedit (BSD)
+ - add hash-map functions: hash-map, map?, assoc, dissoc, get,
+ contains?, keys, vals
+ - add metadata support to List, Vector, HashMap, and Functions
+ - add reader macro
+ - may need to box HashMap and native functions
+ - add atom type, reader macro and functions: with_meta, meta
+ - get `make test^go^stepA` to fully pass
+ - get `./stepA_try ../mal/step1_read_print` to pass
+ - continue for each mal step until ../mal/stepA_try
+ - Now self-hosting!
+
- Extra defintions needed for self-hosting
- core module:
@@ -202,9 +385,6 @@ Step Notes:
- vector, vector?
-- Other misc:
- - conj function
-
- atoms
- reader module:
- @a reader macro -> (deref a)
@@ -221,3 +401,12 @@ Step Notes:
- clone/copy of collections
- core module:
- add with-meta, meta functions
+
+- Other misc:
+ - conj function
+
+- stepA_mal
+ - convert returned data to mal data
+ - recursive, similar to pr_str
+ - Details:
+
diff --git a/forth/Makefile b/forth/Makefile
new file mode 100644
index 0000000..29bf799
--- /dev/null
+++ b/forth/Makefile
@@ -0,0 +1,10 @@
+SOURCES_BASE = types.fs str.fs reader.fs printer.fs
+SOURCES_LISP = env.fs core.fs stepA_mal.fs
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/forth/core.fs b/forth/core.fs
new file mode 100644
index 0000000..1a1cc4d
--- /dev/null
+++ b/forth/core.fs
@@ -0,0 +1,224 @@
+require env.fs
+
+0 MalEnv. constant core
+
+: args-as-native { argv argc -- entry*argc... }
+ argc 0 ?do
+ argv i cells + @ as-native
+ loop ;
+
+: defcore* ( sym xt )
+ MalNativeFn. core env/set ;
+
+: defcore
+ parse-allot-name MalSymbol. ( xt )
+ ['] defcore* :noname ;
+
+defcore + args-as-native + MalInt. ;;
+defcore - args-as-native - MalInt. ;;
+defcore * args-as-native * MalInt. ;;
+defcore / args-as-native / MalInt. ;;
+defcore < args-as-native < mal-bool ;;
+defcore > args-as-native > mal-bool ;;
+defcore <= args-as-native <= mal-bool ;;
+defcore >= args-as-native >= mal-bool ;;
+
+defcore list { argv argc }
+ argc cells allocate throw { start }
+ argv start argc cells cmove
+ start argc MalList. ;;
+
+defcore vector { argv argc }
+ argc cells allocate throw { start }
+ argv start argc cells cmove
+ start argc MalList.
+ MalVector new swap over MalVector/list ! ;;
+
+defcore empty? drop @ empty? ;;
+defcore count drop @ mal-count ;;
+
+defcore = drop dup @ swap cell+ @ swap m= mal-bool ;;
+defcore not
+ drop @
+ dup mal-nil = if
+ drop mal-true
+ else
+ mal-false = if
+ mal-true
+ else
+ mal-false
+ endif
+ endif ;;
+
+: pr-str-multi ( readably? argv argc )
+ ?dup 0= if drop 0 0
+ else
+ { argv argc }
+ new-str
+ argv @ pr-buf
+ argc 1 ?do
+ a-space
+ argv i cells + @ pr-buf
+ loop
+ endif ;
+
+defcore prn true -rot pr-str-multi type cr drop mal-nil ;;
+defcore pr-str true -rot pr-str-multi MalString. nip ;;
+defcore println false -rot pr-str-multi type cr drop mal-nil ;;
+defcore str ( argv argc )
+ dup 0= if
+ MalString.
+ else
+ { argv argc }
+ false new-str
+ argc 0 ?do
+ argv i cells + @ pr-buf
+ loop
+ MalString. nip
+ endif ;;
+
+defcore read-string drop @ unpack-str read-str ;;
+defcore slurp drop @ unpack-str slurp-file MalString. ;;
+
+defcore cons ( argv[item,coll] argc )
+ drop dup @ swap cell+ @ ( item coll )
+ to-list conj ;;
+
+defcore concat { lists argc }
+ MalList new
+ lists over MalList/start !
+ argc over MalList/count !
+ MalList/concat ;;
+
+defcore conj { argv argc }
+ argv @ ( coll )
+ argc 1 ?do
+ argv i cells + @ swap conj
+ loop ;;
+
+defcore assoc { argv argc }
+ argv @ ( coll )
+ argv argc cells + argv cell+ +do
+ i @ \ key
+ i cell+ @ \ val
+ rot assoc
+ 2 cells +loop ;;
+
+defcore keys ( argv argc )
+ drop @ MalMap/list @
+ dup MalList/start @ swap MalList/count @ { start count }
+ here
+ start count cells + start +do
+ i @ ,
+ 2 cells +loop
+ here>MalList ;;
+
+defcore vals ( argv argc )
+ drop @ MalMap/list @
+ dup MalList/start @ swap MalList/count @ { start count }
+ here
+ start count cells + start cell+ +do
+ i @ ,
+ 2 cells +loop
+ here>MalList ;;
+
+defcore dissoc { argv argc }
+ argv @ \ coll
+ argv argc cells + argv cell+ +do
+ i @ swap dissoc
+ cell +loop ;;
+
+defcore hash-map { argv argc }
+ MalMap/Empty
+ argc cells argv + argv +do
+ i @ i cell+ @ rot assoc
+ 2 cells +loop ;;
+
+defcore get { argv argc }
+ argc 3 < if mal-nil else argv cell+ cell+ @ endif
+ argv cell+ @ \ key
+ argv @ \ coll
+ get ;;
+
+defcore contains? { argv argc }
+ 0
+ argv cell+ @ \ key
+ argv @ \ coll
+ get 0 <> mal-bool ;;
+
+defcore nth ( argv[coll,i] argc )
+ drop dup @ to-list ( argv list )
+ swap cell+ @ MalInt/int @ ( list i )
+ over MalList/count @ ( list i count )
+ 2dup >= if { i count }
+ 0 0
+ new-str i int>str str-append s\" \040>= " count int>str
+ s" nth out of bounds: " ...throw-str
+ endif drop ( list i )
+ cells swap ( c-offset list )
+ MalList/start @ + @ ;;
+
+defcore first ( argv[coll] argc )
+ drop @ to-list
+ dup MalList/count @ 0= if
+ drop mal-nil
+ else
+ MalList/start @ @
+ endif ;;
+
+defcore rest ( argv[coll] argc )
+ drop @ to-list MalList/rest ;;
+
+defcore meta ( argv[obj] argc )
+ drop @ mal-meta @
+ ?dup 0= if mal-nil endif ;;
+
+defcore with-meta ( argv[obj,meta] argc )
+ drop ( argv )
+ dup cell+ @ swap @ ( meta obj )
+ dup mal-type @ MalTypeType-struct @ ( meta obj obj-size )
+ dup allocate throw { new-obj } ( meta obj obj-size )
+ new-obj swap cmove ( meta )
+ new-obj mal-meta ! ( )
+ new-obj ;;
+
+defcore atom ( argv[val] argc )
+ drop @ Atom. ;;
+
+defcore deref ( argv[atom] argc )
+ drop @ Atom/val @ ;;
+
+defcore reset! ( argv[atom,val] argc )
+ drop dup cell+ @ ( argv val )
+ dup -rot swap @ Atom/val ! ;;
+
+defcore apply { argv argc -- val }
+ \ argv is (fn args... more-args)
+ argv argc 1- cells + @ to-list { more-args }
+ argc 2 - { list0len }
+ more-args MalList/count @ list0len + { final-argc }
+ final-argc cells allocate throw { final-argv }
+ argv cell+ final-argv list0len cells cmove
+ more-args MalList/start @ final-argv list0len cells + final-argc list0len - cells cmove
+ final-argv final-argc argv @ invoke ;;
+
+defcore throw ( argv argc -- )
+ drop @ to exception-object
+ 1 throw ;;
+
+defcore map? drop @ mal-type @ MalMap = mal-bool ;;
+defcore list? drop @ mal-type @ MalList = mal-bool ;;
+defcore vector? drop @ mal-type @ MalVector = mal-bool ;;
+defcore keyword? drop @ mal-type @ MalKeyword = mal-bool ;;
+defcore symbol? drop @ mal-type @ MalSymbol = mal-bool ;;
+defcore atom? drop @ mal-type @ Atom = mal-bool ;;
+defcore true? drop @ mal-true = mal-bool ;;
+defcore false? drop @ mal-false = mal-bool ;;
+defcore nil? drop @ mal-nil = mal-bool ;;
+
+defcore sequential? drop @ sequential? ;;
+
+defcore keyword drop @ unpack-str MalKeyword. ;;
+defcore symbol drop @ unpack-str MalSymbol. ;;
+
+defcore time-ms 2drop utime d>s 1000 / MalInt. ;;
diff --git a/forth/env.fs b/forth/env.fs
new file mode 100644
index 0000000..9469bf2
--- /dev/null
+++ b/forth/env.fs
@@ -0,0 +1,38 @@
+require types.fs
+
+MalType%
+ cell% field MalEnv/outer
+ cell% field MalEnv/data
+deftype MalEnv
+
+: MalEnv. { outer -- env }
+ MalEnv new { env }
+ outer env MalEnv/outer !
+ MalMap/Empty env MalEnv/data !
+ env ;
+
+: env/set { key val env -- }
+ key val env MalEnv/data @ assoc
+ env MalEnv/data ! ;
+
+: env/get-addr { key env -- val-addr }
+ env
+ begin ( env )
+ key over MalEnv/data @ MalMap/get-addr ( env addr-or-0 )
+ ?dup 0= if ( env )
+ MalEnv/outer @ dup 0= ( env-or-0 done-looping? )
+ else ( env addr )
+ nip -1 \ found it! ( addr -1 )
+ endif
+ until ;
+
+MalEnv
+ extend pr-buf { env }
+ env MalEnv/data @ pr-buf
+ a-space s" outer: " str-append
+ env MalEnv/outer @ ?dup 0= if
+ s" <none>" str-append
+ else
+ pr-buf
+ endif ;;
+drop
diff --git a/forth/misc-tests.fs b/forth/misc-tests.fs
new file mode 100644
index 0000000..6b6d643
--- /dev/null
+++ b/forth/misc-tests.fs
@@ -0,0 +1,100 @@
+require printer.fs
+
+\ === basic testing util === /
+: test=
+ 2dup m= if
+ 2drop
+ else
+ cr ." assert failed on line " sourceline# .
+ swap cr ." | got " . cr ." | expected " . cr
+ endif ;
+
+\ array function tests
+create za 2 , 6 , 7 , 10 , 15 , 80 , 81 ,
+
+7 za 2 array-find -1 test= 0 test=
+7 za 6 array-find -1 test= 1 test=
+7 za 10 array-find -1 test= 3 test=
+7 za 81 array-find -1 test= 6 test=
+7 za 12 array-find 0 test= 4 test=
+7 za 8 array-find 0 test= 3 test=
+7 za 100 array-find 0 test= 7 test=
+7 za 1 array-find 0 test= 0 test=
+6 za 81 array-find 0 test= 6 test=
+
+10 new-array
+1 swap 0 5 array-insert
+2 swap 1 7 array-insert
+3 swap 3 12 array-insert
+4 swap 4 15 array-insert
+5 swap 5 20 array-insert
+
+dup 0 cells + @ 5 test=
+dup 1 cells + @ 7 test=
+dup 2 cells + @ 10 test=
+dup 3 cells + @ 12 test=
+dup 4 cells + @ 15 test=
+dup 5 cells + @ 20 test=
+
+
+\ Protocol tests
+
+: t1
+mal-nil
+42 MalInt. mal-nil conj
+10 MalInt. mal-nil conj conj
+20 MalInt. swap conj
+23 MalInt. mal-nil conj conj conj
+pr-str s" (nil (20 (42) 10) 23)" str= -1 test=
+
+1500 MalInt. 1500 MalInt. test=
+
+\ MalList tests
+
+here 1 MalInt. , 2 MalInt. , 3 MalInt. , here>MalList
+4 MalInt. swap conj
+5 MalInt. swap conj
+pr-str s" (5 4 1 2 3)" str= -1 test=
+
+\ map tests
+
+s" one" MalString. s" one" MalString. test=
+s" one" MalString. s" x" MalString. m= 0 test=
+
+MalMap/Empty
+1000 MalInt. 1100 rot assoc
+2000 MalInt. 2100 rot assoc
+3000 MalInt. 3100 rot assoc
+
+dup 99 2000 MalInt. rot get 2100 test=
+dup 99 4000 MalInt. rot get 99 test=
+drop
+
+MalMap/Empty
+s" one" MalString. s" first" MalString. rot assoc
+s" two" MalString. s" second" MalString. rot assoc
+s" three" MalString. s" third" MalString. rot assoc
+
+dup 99 s" two" MalString. rot get s" second" MalString. test=
+dup 99 s" none" MalString. rot get 99 test=
+drop
+
+99 MalInt. 10 MalInt. MalMap/Empty get 99 MalInt. test=
+
+;
+t1
+
+\ eval tests
+
+require step2_eval.fs
+
+: t2
+mal-nil
+ 1 MalInt. swap conj
+ 2 MalInt. swap conj
+ 3 MalInt. swap conj
+mal-eval
+;
+t2
+
+bye
diff --git a/forth/printer.fs b/forth/printer.fs
new file mode 100644
index 0000000..85f88a0
--- /dev/null
+++ b/forth/printer.fs
@@ -0,0 +1,114 @@
+require str.fs
+require types.fs
+
+\ === printer protocol and implementations === /
+
+def-protocol-method pr-buf ( readably? str-addr str-len this -- str-addr str-len )
+def-protocol-method pr-seq-buf ( readably? str-addr str-len this -- str-addr str-len )
+
+: pr-str { obj }
+ true new-str obj pr-buf rot drop ;
+
+\ Examples of extending existing protocol methods to existing type
+MalDefault
+ extend pr-buf
+ { this }
+ s" #<" str-append
+ this mal-type @ type-name str-append
+ a-space
+ this int>str str-append
+ s" >" str-append ;;
+drop
+
+MalNil extend pr-buf drop s" nil" str-append ;; drop
+MalTrue extend pr-buf drop s" true" str-append ;; drop
+MalFalse extend pr-buf drop s" false" str-append ;; drop
+
+MalList
+ extend pr-buf
+ -rot s" (" str-append ( list str-addr str-len )
+ rot pr-seq-buf
+ s" )" str-append ;;
+ extend pr-seq-buf { list }
+ list MalList/count @ 0 > if
+ list MalList/start @ { start }
+ start @ pr-buf
+ list MalList/count @ 1 ?do
+ a-space
+ start i cells + @ pr-buf
+ loop
+ endif ;;
+drop
+
+MalVector
+ extend pr-buf
+ MalVector/list @
+ -rot s" [" str-append ( list str-addr str-len )
+ rot pr-seq-buf
+ s" ]" str-append ;;
+drop
+
+MalMap
+ extend pr-buf
+ MalMap/list @
+ -rot s" {" str-append ( list str-addr str-len )
+ rot { list }
+ list MalList/count @ { count }
+ count 0 > if
+ list MalList/start @ { start }
+ start @ pr-buf a-space start cell+ @ pr-buf
+ count 2 / 1 ?do
+ s" , " str-append
+ start i 2 * cells + @ pr-buf a-space
+ start i 2 * 1+ cells + @ pr-buf
+ loop
+ endif
+ s" }" str-append ;;
+drop
+
+MalInt
+ extend pr-buf
+ MalInt/int @ int>str str-append ;;
+drop
+
+MalSymbol
+ extend pr-buf
+ unpack-sym str-append ;;
+drop
+
+MalKeyword
+ extend pr-buf { kw }
+ s" :" str-append
+ kw unpack-keyword str-append ;;
+drop
+
+: escape-str { addr len }
+ s\" \"" str-append
+ addr len + addr ?do
+ i c@ case
+ [char] " of s\" \\\"" str-append endof
+ [char] \ of s\" \\\\" str-append endof
+ 10 of s\" \\n" str-append endof
+ 13 of s\" \\r" str-append endof
+ -rot i 1 str-append rot
+ endcase
+ loop
+ s\" \"" str-append ;
+
+MalString
+ extend pr-buf
+ dup MalString/str-addr @
+ swap MalString/str-len @
+ 4 pick if
+ escape-str
+ else
+ str-append
+ endif ;;
+drop
+
+Atom
+ extend pr-buf { this }
+ s" (atom " str-append
+ this Atom/val @ pr-buf
+ s" )" str-append ;;
+drop \ No newline at end of file
diff --git a/forth/reader.fs b/forth/reader.fs
new file mode 100644
index 0000000..134749b
--- /dev/null
+++ b/forth/reader.fs
@@ -0,0 +1,147 @@
+require types.fs
+require printer.fs
+
+\ Drop a char off the front of string by advancing the addr and
+\ decrementing the length, and fetch next char
+: adv-str ( str-addr str-len -- str-addr str-len char )
+ swap 1+ swap 1-
+ dup 0= if 0 ( eof )
+ else over c@ endif ;
+
+: mal-digit? ( char -- flag )
+ dup [char] 9 <= if
+ [char] 0 >=
+ else
+ drop 0
+ endif ;
+
+: char-in-str? ( char str-addr str-len )
+ rot { needle }
+ false -rot
+ over + swap ?do
+ i c@ needle = if drop true leave endif
+ loop ;
+
+: sym-char? ( char -- flag )
+ s\" \n\r\t\000[]{}()'\"`,; " char-in-str? 0= ;
+
+: skip-spaces ( str-addr str-len char -- str-addr str-len non-space-char )
+ begin
+ begin
+ dup s\" \n\r\t, " char-in-str?
+ while ( str-addr str-len space-char )
+ drop adv-str
+ repeat
+ dup [char] ; = if
+ drop
+ begin
+ adv-str s\" \n\r\000" char-in-str?
+ until
+ adv-str false
+ else
+ true
+ endif
+ until ;
+
+defer read-form ( str-addr str-len -- str-addr str-len mal-obj )
+
+: read-int ( str-addr str-len digit-char -- str-addr str-len non-digit-char mal-int )
+ 0 { int }
+ begin ( str-addr str-len digit-char )
+ [char] 0 - int 10 * + to int ( str-addr str-len )
+ adv-str dup mal-digit? 0= ( str-addr str-len digit-char )
+ until
+ int MalInt. ;
+
+: read-symbol-str ( str-addr str-len sym-char -- str-addr str-len char sym-addr sym-len )
+ new-str { sym-addr sym-len }
+ begin ( str-addr str-len sym-char )
+ sym-addr sym-len rot str-append-char to sym-len to sym-addr
+ adv-str dup sym-char? 0=
+ until
+ sym-addr sym-len ;
+
+: read-string-literal ( in-addr in-len quote-char -- in-addr in-len mal-string )
+ new-str { out-addr out-len }
+ drop \ drop leading quote
+ begin ( in-addr in-len )
+ adv-str over 0= if
+ 2drop 0 0 s\" expected '\"', got EOF" ...throw-str
+ endif
+ dup [char] " <>
+ while
+ dup [char] \ = if
+ drop adv-str
+ dup [char] n = if drop 10 endif
+ dup [char] r = if drop 13 endif
+ endif
+ out-addr out-len rot str-append-char to out-len to out-addr
+ repeat
+ drop adv-str \ skip trailing quote
+ out-addr out-len MalString. ;
+
+: read-list ( str-addr str-len open-paren-char close-paren-char
+ -- str-addr str-len non-paren-char mal-list )
+ here { close-char old-here }
+ drop adv-str
+ begin ( str-addr str-len char )
+ skip-spaces ( str-addr str-len non-space-char )
+ over 0= if
+ drop 2drop 0 0 s" ', got EOF"
+ close-char pad ! pad 1
+ s" expected '" ...throw-str
+ endif
+ dup close-char <>
+ while ( str-addr str-len non-space-non-paren-char )
+ read-form ,
+ repeat
+ drop adv-str
+ old-here here>MalList ;
+
+s" deref" MalSymbol. constant deref-sym
+s" quote" MalSymbol. constant quote-sym
+s" quasiquote" MalSymbol. constant quasiquote-sym
+s" splice-unquote" MalSymbol. constant splice-unquote-sym
+s" unquote" MalSymbol. constant unquote-sym
+
+: read-wrapped ( buf-addr buf-len quote-char sym-addr sym-len -- buf-addr buf-len char mal-list )
+ here { old-here }
+ , ( buf-addr buf-len char )
+ read-form , ( buf-addr buf-len char )
+ old-here here>MalList ;
+
+: read-form2 ( str-addr str-len char -- str-addr str-len char mal-obj )
+ skip-spaces
+ dup mal-digit? if read-int else
+ dup [char] ( = if [char] ) read-list else
+ dup [char] [ = if [char] ] read-list MalVector new tuck MalVector/list ! else
+ dup [char] { = if [char] } read-list MalMap new tuck MalMap/list ! else
+ dup [char] " = if read-string-literal else
+ dup [char] : = if drop adv-str read-symbol-str MalKeyword. else
+ dup [char] @ = if drop adv-str deref-sym read-wrapped else
+ dup [char] ' = if drop adv-str quote-sym read-wrapped else
+ dup [char] ` = if drop adv-str quasiquote-sym read-wrapped else
+ dup [char] ~ = if
+ drop adv-str
+ dup [char] @ = if drop adv-str splice-unquote-sym read-wrapped
+ else unquote-sym read-wrapped
+ endif
+ else
+ dup [char] ^ = if
+ drop adv-str
+ read-form { meta } read-form { obj }
+ meta mal-nil conj
+ obj swap conj
+ s" with-meta" MalSymbol. swap conj
+ else
+ read-symbol-str
+ 2dup s" true" str= if 2drop mal-true
+ else 2dup s" false" str= if 2drop mal-false
+ else 2dup s" nil" str= if 2drop mal-nil
+ else
+ MalSymbol.
+ endif endif endif endif endif endif endif endif endif endif endif endif endif endif ;
+' read-form2 is read-form
+
+: read-str ( str-addr str-len - mal-obj )
+ over c@ read-form { obj } drop 2drop obj ;
diff --git a/forth/step0_repl.fs b/forth/step0_repl.fs
new file mode 100644
index 0000000..2483c12
--- /dev/null
+++ b/forth/step0_repl.fs
@@ -0,0 +1,23 @@
+require types.fs
+
+: read ;
+: eval ;
+: print ;
+
+: rep
+ read
+ eval
+ print ;
+
+create buff 128 allot
+
+: read-lines
+ begin
+ ." user> "
+ buff 128 stdin read-line throw
+ while
+ buff swap
+ rep type cr
+ repeat ;
+
+read-lines \ No newline at end of file
diff --git a/forth/step1_read_print.fs b/forth/step1_read_print.fs
new file mode 100644
index 0000000..9e42995
--- /dev/null
+++ b/forth/step1_read_print.fs
@@ -0,0 +1,34 @@
+require reader.fs
+require printer.fs
+
+: read read-str ;
+: eval ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ eval
+ print ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: read-lines
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute safe-type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+read-lines
+cr
+bye
diff --git a/forth/step2_eval.fs b/forth/step2_eval.fs
new file mode 100644
index 0000000..724de44
--- /dev/null
+++ b/forth/step2_eval.fs
@@ -0,0 +1,120 @@
+require reader.fs
+require printer.fs
+
+: args-as-native { argv argc -- entry*argc... }
+ argc 0 ?do
+ argv i cells + @ as-native
+ loop ;
+
+: env-assoc ( map sym-str-addr sym-str-len xt )
+ -rot MalSymbol. swap MalNativeFn. rot assoc ;
+
+MalMap/Empty
+ s" +" :noname args-as-native + MalInt. ; env-assoc
+ s" -" :noname args-as-native - MalInt. ; env-assoc
+ s" *" :noname args-as-native * MalInt. ; env-assoc
+ s" /" :noname args-as-native / MalInt. ; env-assoc
+constant repl-env
+
+: read read-str ;
+: eval ( env obj ) mal-eval ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ 0 sym env get
+ dup 0= if
+ drop
+ ." Symbol '"
+ sym pr-str safe-type
+ ." ' not found." cr
+ 1 throw
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: read-lines
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute safe-type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+read-lines
+cr
+bye
diff --git a/forth/step3_env.fs b/forth/step3_env.fs
new file mode 100644
index 0000000..a8a625e
--- /dev/null
+++ b/forth/step3_env.fs
@@ -0,0 +1,154 @@
+require reader.fs
+require printer.fs
+require env.fs
+
+: args-as-native { argv argc -- entry*argc... }
+ argc 0 ?do
+ argv i cells + @ as-native
+ loop ;
+
+0 MalEnv. constant repl-env
+s" +" MalSymbol. :noname args-as-native + MalInt. ; MalNativeFn. repl-env env/set
+s" -" MalSymbol. :noname args-as-native - MalInt. ; MalNativeFn. repl-env env/set
+s" *" MalSymbol. :noname args-as-native * MalInt. ; MalNativeFn. repl-env env/set
+s" /" MalSymbol. :noname args-as-native / MalInt. ; MalNativeFn. repl-env env/set
+
+: read read-str ;
+: eval ( env obj ) mal-eval ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ eval
+ \ TODO: dec refcount of env
+ ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ ." Symbol '" sym pr-str safe-type ." ' not found." cr
+ 1 throw
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: read-lines
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute safe-type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+read-lines
+cr
+bye
diff --git a/forth/step4_if_fn_do.fs b/forth/step4_if_fn_do.fs
new file mode 100644
index 0000000..a3d64ac
--- /dev/null
+++ b/forth/step4_if_fn_do.fs
@@ -0,0 +1,214 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+: read read-str ;
+: eval ( env obj ) mal-eval ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @
+ 0
+ list MalList/count @ 1 ?do
+ drop
+ dup i cells + @ env swap eval
+ loop
+ nip ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ call-env list eval-rest { argv argc }
+
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ f-args i 1+ cells + @ ( more-args-symbol )
+ MalList new ( sym more-args )
+ argc i - dup { c } over MalList/count !
+ c cells allocate throw dup { start } over MalList/start !
+ argv i cells + start c cells cmove
+ env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+
+ env mal-fn MalUserFn/body @ eval ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ ." Symbol '" sym pr-str safe-type ." ' not found." cr
+ 1 throw
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: read-lines
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute safe-type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+read-lines
+cr
+bye
diff --git a/forth/step5_tco.fs b/forth/step5_tco.fs
new file mode 100644
index 0000000..421a2fc
--- /dev/null
+++ b/forth/step5_tco.fs
@@ -0,0 +1,225 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+99999999 constant TCO-eval
+
+: read read-str ;
+: eval ( env obj )
+ begin
+ \ ." eval-> " dup pr-str safe-type cr
+ mal-eval
+ dup TCO-eval =
+ while
+ drop
+ repeat ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ TCO-eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ TCO-eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @ { start }
+ list MalList/count @ dup 1- { last } 1 ?do
+ env start i cells + @
+ i last = if
+ TCO-eval
+ else
+ eval drop
+ endif
+ loop ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ TCO-eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ TCO-eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ call-env list eval-rest { argv argc }
+
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ f-args i 1+ cells + @ ( more-args-symbol )
+ MalList new ( sym more-args )
+ argc i - dup { c } over MalList/count !
+ c cells allocate throw dup { start } over MalList/start !
+ argv i cells + start c cells cmove
+ env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+
+ env mal-fn MalUserFn/body @ TCO-eval ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ ." Symbol '" sym pr-str safe-type ." ' not found." cr
+ 1 throw
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: read-lines
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute safe-type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+read-lines
+cr
+bye
diff --git a/forth/step6_file.fs b/forth/step6_file.fs
new file mode 100644
index 0000000..60b3817
--- /dev/null
+++ b/forth/step6_file.fs
@@ -0,0 +1,252 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+99999999 constant TCO-eval
+
+: read read-str ;
+: eval ( env obj )
+ begin
+ \ ." eval-> " dup pr-str safe-type cr
+ mal-eval
+ dup TCO-eval =
+ while
+ drop
+ repeat ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ TCO-eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ TCO-eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @ { start }
+ list MalList/count @ dup 1- { last } 1 ?do
+ env start i cells + @
+ i last = if
+ TCO-eval
+ else
+ eval drop
+ endif
+ loop ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ TCO-eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ TCO-eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ call-env list eval-rest { argv argc }
+
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ f-args i 1+ cells + @ ( more-args-symbol )
+ MalList new ( sym more-args )
+ argc i - dup { c } over MalList/count !
+ c cells allocate throw dup { start } over MalList/start !
+ argv i cells + start c cells cmove
+ env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+
+ env mal-fn MalUserFn/body @ TCO-eval ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ ." Symbol '" sym pr-str safe-type ." ' not found." cr
+ 1 throw
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+defcore eval ( argv argc )
+ drop @ repl-env swap eval ;;
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+: mk-args-list ( -- )
+ here
+ begin
+ next-arg 2dup 0 0 d<> while
+ MalString. ,
+ repeat
+ 2drop here>MalList ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop
+
+: repl ( -- )
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+: main ( -- )
+ mk-args-list { args-list }
+ args-list MalList/count @ 0= if
+ s" *ARGV*" MalSymbol. MalList/Empty repl-env env/set
+ repl
+ else
+ args-list MalList/start @ @ { filename }
+ s" *ARGV*" MalSymbol. args-list MalList/rest repl-env env/set
+
+ repl-env
+ here s" load-file" MalSymbol. , filename , here>MalList
+ eval print
+ endif ;
+
+main
+cr
+bye
diff --git a/forth/step7_quote.fs b/forth/step7_quote.fs
new file mode 100644
index 0000000..1e4043d
--- /dev/null
+++ b/forth/step7_quote.fs
@@ -0,0 +1,294 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+99999999 constant TCO-eval
+
+: read read-str ;
+: eval ( env obj )
+ begin
+ \ ." eval-> " dup pr-str safe-type cr
+ mal-eval
+ dup TCO-eval =
+ while
+ drop
+ repeat ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ TCO-eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+: is-pair? ( obj -- bool )
+ empty? mal-false = ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+s" concat" MalSymbol. constant concat-sym
+s" cons" MalSymbol. constant cons-sym
+
+defer quasiquote
+: quasiquote0 { ast -- form }
+ ast is-pair? 0= if
+ here quote-sym , ast , here>MalList
+ else
+ ast to-list MalList/start @ { ast-start }
+ ast-start @ { ast[0] }
+ ast[0] unquote-sym m= if
+ ast-start cell+ @
+ else
+ ast[0] is-pair? if
+ ast[0] to-list MalList/start @ { ast[0]-start }
+ ast[0]-start @ splice-unquote-sym m= if
+ here
+ concat-sym ,
+ ast[0]-start cell+ @ ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ false
+ else true endif
+ else true endif
+ if
+ here
+ cons-sym ,
+ ast[0] quasiquote ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ endif
+ endif
+ endif ;
+' quasiquote0 is quasiquote
+
+defspecial quasiquote ( env list )
+ MalList/start @ cell+ @ ( ast )
+ quasiquote TCO-eval ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ TCO-eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @ { start }
+ list MalList/count @ dup 1- { last } 1 ?do
+ env start i cells + @
+ i last = if
+ TCO-eval
+ else
+ eval drop
+ endif
+ loop ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ TCO-eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ TCO-eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ call-env list eval-rest { argv argc }
+
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ f-args i 1+ cells + @ ( more-args-symbol )
+ MalList new ( sym more-args )
+ argc i - dup { c } over MalList/count !
+ c cells allocate throw dup { start } over MalList/start !
+ argv i cells + start c cells cmove
+ env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+
+ env mal-fn MalUserFn/body @ TCO-eval ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ ." Symbol '" sym pr-str safe-type ." ' not found." cr
+ 1 throw
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+defcore eval ( argv argc )
+ drop @ repl-env swap eval ;;
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+: mk-args-list ( -- )
+ here
+ begin
+ next-arg 2dup 0 0 d<> while
+ MalString. ,
+ repeat
+ 2drop here>MalList ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop
+
+: repl ( -- )
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+: main ( -- )
+ mk-args-list { args-list }
+ args-list MalList/count @ 0= if
+ s" *ARGV*" MalSymbol. MalList/Empty repl-env env/set
+ repl
+ else
+ args-list MalList/start @ @ { filename }
+ s" *ARGV*" MalSymbol. args-list MalList/rest repl-env env/set
+
+ repl-env
+ here s" load-file" MalSymbol. , filename , here>MalList
+ eval print
+ endif ;
+
+main
+cr
+bye
diff --git a/forth/step8_macros.fs b/forth/step8_macros.fs
new file mode 100644
index 0000000..7260567
--- /dev/null
+++ b/forth/step8_macros.fs
@@ -0,0 +1,324 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+99999999 constant TCO-eval
+
+: read read-str ;
+: eval ( env obj )
+ begin
+ \ ." eval-> " dup pr-str safe-type cr
+ mal-eval
+ dup TCO-eval =
+ while
+ drop
+ repeat ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ TCO-eval
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke ( env list this -- list )
+ MalNativeFn/xt @ { xt }
+ eval-rest ( argv argc )
+ xt execute ( return-val ) ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+: is-pair? ( obj -- bool )
+ empty? mal-false = ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+s" concat" MalSymbol. constant concat-sym
+s" cons" MalSymbol. constant cons-sym
+
+defer quasiquote
+: quasiquote0 { ast -- form }
+ ast is-pair? 0= if
+ here quote-sym , ast , here>MalList
+ else
+ ast to-list MalList/start @ { ast-start }
+ ast-start @ { ast[0] }
+ ast[0] unquote-sym m= if
+ ast-start cell+ @
+ else
+ ast[0] is-pair? if
+ ast[0] to-list MalList/start @ { ast[0]-start }
+ ast[0]-start @ splice-unquote-sym m= if
+ here
+ concat-sym ,
+ ast[0]-start cell+ @ ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ false
+ else true endif
+ else true endif
+ if
+ here
+ cons-sym ,
+ ast[0] quasiquote ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ endif
+ endif
+ endif ;
+' quasiquote0 is quasiquote
+
+defspecial quasiquote ( env list )
+ MalList/start @ cell+ @ ( ast )
+ quasiquote TCO-eval ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial defmacro! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval { val }
+ true val MalUserFn/is-macro? !
+ val env env/set
+ val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ TCO-eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @ { start }
+ list MalList/count @ dup 1- { last } 1 ?do
+ env start i cells + @
+ i last = if
+ TCO-eval
+ else
+ eval drop
+ endif
+ loop ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ TCO-eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ TCO-eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+: new-user-fn-env { argv argc mal-fn -- env }
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ f-args i 1+ cells + @ ( more-args-symbol )
+ MalList new ( sym more-args )
+ argc i - dup { c } over MalList/count !
+ c cells allocate throw dup { start } over MalList/start !
+ argv i cells + start c cells cmove
+ env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+ env ;
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ mal-fn MalUserFn/is-macro? @ if
+ list MalList/start @ cell+ list MalList/count @ 1-
+ else
+ call-env list eval-rest
+ endif
+ mal-fn new-user-fn-env { env }
+
+ mal-fn MalUserFn/is-macro? @ if
+ env mal-fn MalUserFn/body @ eval
+ env swap TCO-eval
+ else
+ env mal-fn MalUserFn/body @ TCO-eval
+ endif ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+defspecial macroexpand ( env list[_,form] -- form )
+ MalList/start @ cell+ @ swap over ( form env form )
+ MalList/start @ @ ( form env macro-name-expr )
+ eval { macro-fn } ( form )
+ dup MalList/start @ cell+ swap MalList/count @ 1- macro-fn ( argv argc fn )
+ new-user-fn-env ( env )
+ macro-fn MalUserFn/body @ TCO-eval ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ ." Symbol '" sym pr-str safe-type ." ' not found." cr
+ 1 throw
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+defcore eval ( argv argc )
+ drop @ repl-env swap eval ;;
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+: mk-args-list ( -- )
+ here
+ begin
+ next-arg 2dup 0 0 d<> while
+ MalString. ,
+ repeat
+ 2drop here>MalList ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop
+s\" (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 2drop
+s\" (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))))))))" rep 2drop
+
+: repl ( -- )
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute type
+ catch ?dup 0= if safe-type else ." Caught error " . endif
+ cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ repeat ;
+
+: main ( -- )
+ mk-args-list { args-list }
+ args-list MalList/count @ 0= if
+ s" *ARGV*" MalSymbol. MalList/Empty repl-env env/set
+ repl
+ else
+ args-list MalList/start @ @ { filename }
+ s" *ARGV*" MalSymbol. args-list MalList/rest repl-env env/set
+
+ repl-env
+ here s" load-file" MalSymbol. , filename , here>MalList
+ eval print
+ endif ;
+
+main
+cr
+bye
diff --git a/forth/step9_try.fs b/forth/step9_try.fs
new file mode 100644
index 0000000..681e608
--- /dev/null
+++ b/forth/step9_try.fs
@@ -0,0 +1,381 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+99999999 constant TCO-eval
+
+: read read-str ;
+: eval ( env obj )
+ begin
+ \ ." eval-> " dup pr-str safe-type cr
+ mal-eval
+ dup TCO-eval =
+ while
+ drop
+ repeat ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ TCO-eval
+ else
+ mal-nil
+ endif
+ endif ;;
+ extend invoke { argv argc kw -- val }
+ 0 kw argv @ get
+ ?dup 0= if
+ argc 1 > if
+ argv cell+ @
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke { env list this -- list }
+ env list eval-rest ( argv argc )
+ this invoke ;;
+ extend invoke ( argv argc this -- val )
+ MalNativeFn/xt @ execute ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+: is-pair? ( obj -- bool )
+ empty? mal-false = ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+s" concat" MalSymbol. constant concat-sym
+s" cons" MalSymbol. constant cons-sym
+
+defer quasiquote
+: quasiquote0 { ast -- form }
+ ast is-pair? 0= if
+ here quote-sym , ast , here>MalList
+ else
+ ast to-list MalList/start @ { ast-start }
+ ast-start @ { ast[0] }
+ ast[0] unquote-sym m= if
+ ast-start cell+ @
+ else
+ ast[0] is-pair? if
+ ast[0] to-list MalList/start @ { ast[0]-start }
+ ast[0]-start @ splice-unquote-sym m= if
+ here
+ concat-sym ,
+ ast[0]-start cell+ @ ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ false
+ else true endif
+ else true endif
+ if
+ here
+ cons-sym ,
+ ast[0] quasiquote ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ endif
+ endif
+ endif ;
+' quasiquote0 is quasiquote
+
+defspecial quasiquote ( env list )
+ MalList/start @ cell+ @ ( ast )
+ quasiquote TCO-eval ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial defmacro! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval { val }
+ true val MalUserFn/is-macro? !
+ val env env/set
+ val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ TCO-eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @ { start }
+ list MalList/count @ dup 1- { last } 1 ?do
+ env start i cells + @
+ i last = if
+ TCO-eval
+ else
+ eval drop
+ endif
+ loop ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ TCO-eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ TCO-eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+: new-user-fn-env { argv argc mal-fn -- env }
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ argc i - { c }
+ c cells allocate throw { start }
+ argv i cells + start c cells cmove
+ f-args i 1+ cells + @ ( more-args-symbol )
+ start c MalList. env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+ env ;
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ mal-fn MalUserFn/is-macro? @ if
+ list MalList/start @ cell+ \ argv
+ list MalList/count @ 1- \ argc
+ mal-fn new-user-fn-env { env }
+ env mal-fn MalUserFn/body @ eval
+ call-env swap TCO-eval
+ else
+ call-env list eval-rest
+ mal-fn invoke
+ endif ;;
+
+ extend invoke ( argv argc mal-fn )
+ dup { mal-fn } new-user-fn-env { env }
+ env mal-fn MalUserFn/body @ TCO-eval ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ false over MalUserFn/is-macro? !
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+defspecial macroexpand ( env list[_,form] -- form )
+ MalList/start @ cell+ @ swap over ( form env form )
+ MalList/start @ @ ( form env macro-name-expr )
+ eval { macro-fn } ( form )
+ dup MalList/start @ cell+ swap MalList/count @ 1- macro-fn ( argv argc fn )
+ new-user-fn-env ( env )
+ macro-fn MalUserFn/body @ TCO-eval ;;
+
+5555555555 constant pre-try
+
+defspecial try* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ pre-try
+ env arg0 @ ['] eval catch ?dup 0= if
+ nip
+ else { errno }
+ begin pre-try = until
+ errno 1 <> if
+ s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc
+ to exception-object
+ endif
+ arg0 cell+ @ ( list[catch*,sym,form] )
+ MalList/start @ cell+ { catch0 }
+ env MalEnv. { catch-env }
+ catch0 @ exception-object catch-env env/set
+ catch-env catch0 cell+ @ TCO-eval
+ endif ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ 0 0 s" ' not found" sym pr-str s" '" ...throw-str
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+defcore eval ( argv argc )
+ drop @ repl-env swap eval ;;
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+: mk-args-list ( -- )
+ here
+ begin
+ next-arg 2dup 0 0 d<> while
+ MalString. ,
+ repeat
+ 2drop here>MalList ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: nop ;
+
+defcore map ( argv argc -- list )
+ drop dup @ swap cell+ @ to-list { fn list }
+ here
+ list MalList/start @ list MalList/count @ cells over + swap +do
+ i 1 fn invoke
+ dup TCO-eval = if drop eval endif
+ ,
+ cell +loop
+ here>MalList ;;
+
+defcore readline ( argv argc -- mal-string )
+ drop @ unpack-str type stdout flush-file drop
+ buff 128 stdin read-line throw
+ if buff swap MalString. else drop mal-nil endif ;;
+
+s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop
+s\" (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 2drop
+s\" (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))))))))" rep 2drop
+s\" (def! swap! (fn* [a f & args] (reset! a (apply f @a args))))" rep 2drop
+
+: repl ( -- )
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute ['] nop \ uncomment to see stack traces
+ catch ?dup 0= if
+ safe-type cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ else { errno }
+ begin stack-leak-detect = until
+ errno 1 <> if
+ s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc
+ to exception-object
+ endif
+ ." Uncaught exception: "
+ exception-object pr-str safe-type cr
+ endif
+ repeat ;
+
+: main ( -- )
+ mk-args-list { args-list }
+ args-list MalList/count @ 0= if
+ s" *ARGV*" MalSymbol. MalList/Empty repl-env env/set
+ repl
+ else
+ args-list MalList/start @ @ { filename }
+ s" *ARGV*" MalSymbol. args-list MalList/rest repl-env env/set
+
+ repl-env
+ here s" load-file" MalSymbol. , filename , here>MalList
+ eval print
+ endif ;
+
+main
+cr
+bye
diff --git a/forth/stepA_mal.fs b/forth/stepA_mal.fs
new file mode 100644
index 0000000..af5f5d8
--- /dev/null
+++ b/forth/stepA_mal.fs
@@ -0,0 +1,390 @@
+require reader.fs
+require printer.fs
+require core.fs
+
+core MalEnv. constant repl-env
+
+99999999 constant TCO-eval
+
+: read read-str ;
+: eval ( env obj )
+ begin
+ \ ." eval-> " dup pr-str safe-type cr
+ mal-eval
+ dup TCO-eval =
+ while
+ drop
+ repeat ;
+: print
+ \ ." Type: " dup mal-type @ type-name safe-type cr
+ pr-str ;
+
+MalDefault extend mal-eval nip ;; drop \ By default, evalutate to yourself
+
+MalKeyword
+ extend eval-invoke { env list kw -- val }
+ 0 kw env list MalList/start @ cell+ @ eval get
+ ?dup 0= if
+ \ compute not-found value
+ list MalList/count @ 1 > if
+ env list MalList/start @ 2 cells + @ TCO-eval
+ else
+ mal-nil
+ endif
+ endif ;;
+ extend invoke { argv argc kw -- val }
+ 0 kw argv @ get
+ ?dup 0= if
+ argc 1 > if
+ argv cell+ @
+ else
+ mal-nil
+ endif
+ endif ;;
+drop
+
+\ eval all but the first item of list
+: eval-rest { env list -- argv argc }
+ list MalList/start @ cell+ { expr-start }
+ list MalList/count @ 1- { argc }
+ argc cells allocate throw { target }
+ argc 0 ?do
+ env expr-start i cells + @ eval
+ target i cells + !
+ loop
+ target argc ;
+
+MalNativeFn
+ extend eval-invoke { env list this -- list }
+ env list eval-rest ( argv argc )
+ this invoke ;;
+ extend invoke ( argv argc this -- val )
+ MalNativeFn/xt @ execute ;;
+drop
+
+SpecialOp
+ extend eval-invoke ( env list this -- list )
+ SpecialOp/xt @ execute ;;
+drop
+
+: install-special ( symbol xt )
+ SpecialOp. repl-env env/set ;
+
+: defspecial
+ parse-allot-name MalSymbol.
+ ['] install-special
+ :noname
+ ;
+
+: is-pair? ( obj -- bool )
+ empty? mal-false = ;
+
+defspecial quote ( env list -- form )
+ nip MalList/start @ cell+ @ ;;
+
+s" concat" MalSymbol. constant concat-sym
+s" cons" MalSymbol. constant cons-sym
+
+defer quasiquote
+: quasiquote0 { ast -- form }
+ ast is-pair? 0= if
+ here quote-sym , ast , here>MalList
+ else
+ ast to-list MalList/start @ { ast-start }
+ ast-start @ { ast[0] }
+ ast[0] unquote-sym m= if
+ ast-start cell+ @
+ else
+ ast[0] is-pair? if
+ ast[0] to-list MalList/start @ { ast[0]-start }
+ ast[0]-start @ splice-unquote-sym m= if
+ here
+ concat-sym ,
+ ast[0]-start cell+ @ ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ false
+ else true endif
+ else true endif
+ if
+ here
+ cons-sym ,
+ ast[0] quasiquote ,
+ ast to-list MalList/rest quasiquote ,
+ here>MalList
+ endif
+ endif
+ endif ;
+' quasiquote0 is quasiquote
+
+defspecial quasiquote ( env list )
+ MalList/start @ cell+ @ ( ast )
+ quasiquote TCO-eval ;;
+
+defspecial def! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval dup { val } ( key val )
+ env env/set val ;;
+
+defspecial defmacro! { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ arg0 @ ( key )
+ env arg0 cell+ @ eval { val }
+ true val MalUserFn/is-macro? !
+ val env env/set
+ val ;;
+
+defspecial let* { old-env list -- val }
+ old-env MalEnv. { env }
+ list MalList/start @ cell+ dup { arg0 }
+ @ to-list
+ dup MalList/start @ { bindings-start } ( list )
+ MalList/count @ 0 +do
+ bindings-start i cells + dup @ swap cell+ @ ( sym expr )
+ env swap eval
+ env env/set
+ 2 +loop
+ env arg0 cell+ @ TCO-eval
+ \ TODO: dec refcount of env
+ ;;
+
+defspecial do { env list -- val }
+ list MalList/start @ { start }
+ list MalList/count @ dup 1- { last } 1 ?do
+ env start i cells + @
+ i last = if
+ TCO-eval
+ else
+ eval drop
+ endif
+ loop ;;
+
+defspecial if { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ env arg0 @ eval ( test-val )
+ dup mal-false = if
+ drop -1
+ else
+ mal-nil =
+ endif
+ if
+ \ branch to false
+ list MalList/count @ 3 > if
+ env arg0 cell+ cell+ @ TCO-eval
+ else
+ mal-nil
+ endif
+ else
+ \ branch to true
+ env arg0 cell+ @ TCO-eval
+ endif ;;
+
+s" &" MalSymbol. constant &-sym
+
+: new-user-fn-env { argv argc mal-fn -- env }
+ mal-fn MalUserFn/formal-args @ { f-args-list }
+ mal-fn MalUserFn/env @ MalEnv. { env }
+
+ f-args-list MalList/start @ { f-args }
+ f-args-list MalList/count @ ?dup 0= if else
+ \ pass nil for last arg, unless overridden below
+ 1- cells f-args + @ mal-nil env env/set
+ endif
+ argc 0 ?do
+ f-args i cells + @
+ dup &-sym m= if
+ drop
+ argc i - { c }
+ c cells allocate throw { start }
+ argv i cells + start c cells cmove
+ f-args i 1+ cells + @ ( more-args-symbol )
+ start c MalList. env env/set
+ leave
+ endif
+ argv i cells + @
+ env env/set
+ loop
+ env ;
+
+MalUserFn
+ extend eval-invoke { call-env list mal-fn -- list }
+ mal-fn MalUserFn/is-macro? @ if
+ list MalList/start @ cell+ \ argv
+ list MalList/count @ 1- \ argc
+ mal-fn new-user-fn-env { env }
+ env mal-fn MalUserFn/body @ eval
+ call-env swap TCO-eval
+ else
+ call-env list eval-rest
+ mal-fn invoke
+ endif ;;
+
+ extend invoke ( argv argc mal-fn )
+ dup { mal-fn } new-user-fn-env { env }
+ env mal-fn MalUserFn/body @ TCO-eval ;;
+drop
+
+defspecial fn* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ MalUserFn new
+ false over MalUserFn/is-macro? !
+ env over MalUserFn/env !
+ arg0 @ to-list over MalUserFn/formal-args !
+ arg0 cell+ @ over MalUserFn/body ! ;;
+
+defspecial macroexpand ( env list[_,form] -- form )
+ MalList/start @ cell+ @ swap over ( form env form )
+ MalList/start @ @ ( form env macro-name-expr )
+ eval { macro-fn } ( form )
+ dup MalList/start @ cell+ swap MalList/count @ 1- macro-fn ( argv argc fn )
+ new-user-fn-env ( env )
+ macro-fn MalUserFn/body @ TCO-eval ;;
+
+5555555555 constant pre-try
+
+defspecial try* { env list -- val }
+ list MalList/start @ cell+ { arg0 }
+ pre-try
+ env arg0 @ ['] eval catch ?dup 0= if
+ nip
+ else { errno }
+ begin pre-try = until
+ errno 1 <> if
+ s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc
+ to exception-object
+ endif
+ arg0 cell+ @ ( list[catch*,sym,form] )
+ MalList/start @ cell+ { catch0 }
+ env MalEnv. { catch-env }
+ catch0 @ exception-object catch-env env/set
+ catch-env catch0 cell+ @ TCO-eval
+ endif ;;
+
+defspecial . { env coll -- rtn-list }
+ depth { old-depth }
+ coll to-list dup MalList/count @ swap MalList/start @ { count start }
+ count cells start + start cell+ +do
+ env i @ eval as-native
+ cell +loop ;;
+
+MalSymbol
+ extend mal-eval { env sym -- val }
+ sym env env/get-addr
+ dup 0= if
+ drop
+ 0 0 s" ' not found" sym pr-str s" '" ...throw-str
+ else
+ @
+ endif ;;
+drop
+
+: eval-ast { env list -- list }
+ here
+ list MalList/start @ { expr-start }
+ list MalList/count @ 0 ?do
+ env expr-start i cells + @ eval ,
+ loop
+ here>MalList ;
+
+MalList
+ extend mal-eval { env list -- val }
+ env list MalList/start @ @ eval
+ env list rot eval-invoke ;;
+drop
+
+MalVector
+ extend mal-eval ( env vector -- vector )
+ MalVector/list @ eval-ast
+ MalVector new swap over MalVector/list ! ;;
+drop
+
+MalMap
+ extend mal-eval ( env map -- map )
+ MalMap/list @ eval-ast
+ MalMap new swap over MalMap/list ! ;;
+drop
+
+defcore eval ( argv argc )
+ drop @ repl-env swap eval ;;
+
+: rep ( str-addr str-len -- str-addr str-len )
+ read
+ repl-env swap eval
+ print ;
+
+: mk-args-list ( -- )
+ here
+ begin
+ next-arg 2dup 0 0 d<> while
+ MalString. ,
+ repeat
+ 2drop here>MalList ;
+
+create buff 128 allot
+77777777777 constant stack-leak-detect
+
+: nop ;
+
+defcore map ( argv argc -- list )
+ drop dup @ swap cell+ @ to-list { fn list }
+ here
+ list MalList/start @ list MalList/count @ cells over + swap +do
+ i 1 fn invoke
+ dup TCO-eval = if drop eval endif
+ ,
+ cell +loop
+ here>MalList ;;
+
+defcore readline ( argv argc -- mal-string )
+ drop @ unpack-str type stdout flush-file drop
+ buff 128 stdin read-line throw
+ if buff swap MalString. else drop mal-nil endif ;;
+
+s\" (def! *host-language* \"forth\")" rep 2drop
+s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop
+s\" (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 2drop
+s\" (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))))))))" rep 2drop
+s\" (def! swap! (fn* [a f & args] (reset! a (apply f @a args))))" rep 2drop
+
+: repl ( -- )
+ s\" (println (str \"Mal [\" *host-language* \"]\"))" rep 2drop
+ begin
+ ." user> "
+ stack-leak-detect
+ buff 128 stdin read-line throw
+ while ( num-bytes-read )
+ buff swap ( str-addr str-len )
+ ['] rep
+ \ execute ['] nop \ uncomment to see stack traces
+ catch ?dup 0= if
+ safe-type cr
+ stack-leak-detect <> if ." --stack leak--" cr endif
+ else { errno }
+ begin stack-leak-detect = until
+ errno 1 <> if
+ s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc
+ to exception-object
+ endif
+ ." Uncaught exception: "
+ exception-object pr-str safe-type cr
+ endif
+ repeat ;
+
+: main ( -- )
+ mk-args-list { args-list }
+ args-list MalList/count @ 0= if
+ s" *ARGV*" MalSymbol. MalList/Empty repl-env env/set
+ repl
+ else
+ args-list MalList/start @ @ { filename }
+ s" *ARGV*" MalSymbol. args-list MalList/rest repl-env env/set
+
+ repl-env
+ here s" load-file" MalSymbol. , filename , here>MalList
+ eval print
+ endif ;
+
+main
+cr
+bye
diff --git a/forth/str.fs b/forth/str.fs
new file mode 100644
index 0000000..20aef32
--- /dev/null
+++ b/forth/str.fs
@@ -0,0 +1,73 @@
+: safe-type ( str-addr str-len -- )
+ dup 256 > if
+ drop 256 type ." ...<lots more>"
+ else
+ type
+ endif ;
+
+\ === mutable string buffer === /
+\ string buffer that maintains an allocation larger than the current
+\ string size. When appending would cause the string size exceed the
+\ current allocation, resize is used to double the allocation. The
+\ current allocation is not stored anywhere, but computed based on
+\ current string size or str-base-size, whichever is larger.
+64 constant str-base-size
+
+: new-str ( -- addr length )
+ str-base-size allocate throw 0 ;
+
+: round-up ( n -- n )
+ 2
+ begin
+ 1 lshift 2dup <
+ until
+ nip ;
+
+: str-append { buf-addr buf-str-len str-addr str-len }
+ buf-str-len str-len +
+ { new-len }
+ new-len str-base-size >= if
+ buf-str-len new-len xor buf-str-len > if
+ buf-addr new-len round-up resize throw
+ to buf-addr
+ endif
+ endif
+ str-addr buf-addr buf-str-len + str-len cmove
+ buf-addr new-len ;
+
+\ define a-space, to append a space char to a string
+bl c,
+here constant space-str
+: a-space space-str 1 str-append ;
+
+: str-append-char ( buf-addr buf-str-len char -- buf-addr buf-str-len )
+ pad ! pad 1 str-append ;
+
+\ from gforth docs, there named 'my-.'
+: int>str ( num -- str-addr str-len )
+ \ handling negatives.. behaves like Standard .
+ s>d \ convert to signed double
+ swap over dabs \ leave sign byte followed by unsigned double
+ <<# \ start conversion
+ #s \ convert all digits
+ rot sign \ get at sign byte, append "-" if needed
+ #> \ complete conversion
+ #>> ; \ release hold area
+
+defer MalString.
+
+: ...str
+ new-str
+ begin
+ 2swap
+ over 0 <>
+ while
+ str-append
+ repeat
+ 2drop MalString. ;
+
+nil value exception-object
+
+: ...throw-str
+ ...str to exception-object
+ 1 throw ;
diff --git a/forth/tests/stepA_mal.mal b/forth/tests/stepA_mal.mal
new file mode 100644
index 0000000..c4a0e75
--- /dev/null
+++ b/forth/tests/stepA_mal.mal
@@ -0,0 +1,41 @@
+;; Basic interop
+(. 5 'MalInt.)
+;=>5
+(. 11 31 '+ 'MalInt.)
+;=>42
+(. "greetings" 'MalString.)
+;=>"greetings"
+(. "hello" 'type 'cr 'mal-nil)
+; hello
+;=>nil
+
+;; Interop on non-literals
+(. (+ 15 27) 'MalInt.)
+;=>42
+(let* [a 17] (. a 25 '+ 'MalInt.))
+;=>42
+(let* [a "hello"] (. a 1 '- 'MalString.))
+;=>"hell"
+
+;; Use of annoyingly-named forth words
+(. 1 'MalInt. (symbol ",") 'here (symbol "@"))
+;=>1
+(let* (i 'MalInt.) (. 5 i))
+;=>5
+(let* (comma (symbol ",") fetch (symbol "@")) (. 'here 42 'MalInt. comma fetch))
+;=>42
+
+;; Multiple .-forms interacting via heap memory and mal locals
+(def! string-parts (fn* (s) (. s 'MalInt. 'swap 'MalInt. 'here '-rot (symbol ",") (symbol ",") 'here>MalList)))
+(first (rest (string-parts "sketchy")))
+;=>7
+(def! prn-chars (fn* (start count) (if (> count 0) (do (prn (. start 1 'MalString.)) (prn-chars (+ start 1) (- count 1))))))
+(let* (msg (string-parts "sketchy")) (prn-chars (first msg) (first (rest msg))))
+; "s"
+; "k"
+; "e"
+; "t"
+; "c"
+; "h"
+; "y"
+;=>nil
diff --git a/forth/types.fs b/forth/types.fs
new file mode 100644
index 0000000..2fceccf
--- /dev/null
+++ b/forth/types.fs
@@ -0,0 +1,624 @@
+require str.fs
+
+\ === sorted-array === /
+\ Here are a few utility functions useful for creating and maintaining
+\ the deftype* method tables. The keys array is kept in sorted order,
+\ and the methods array is maintained in parallel so that an index into
+\ one corresponds to an index in the other.
+
+\ Search a sorted array for key, returning the index of where it was
+\ found. If key is not in the array, return the index where it would
+\ be if added.
+: array-find { a-length a-addr key -- index found? }
+ 0 a-length ( start end )
+ begin
+ \ cr 2dup . .
+ 2dup + 2 / dup ( start end middle middle )
+ cells a-addr + @ ( start end middle mid-val )
+ dup key < if
+ drop rot ( end middle start )
+ 2dup = if
+ 2drop dup ( end end )
+ else
+ drop swap ( middle end )
+ endif
+ else
+ key > if ( start end middle )
+ nip ( start middle )
+ else
+ -rot 2drop dup ( middle middle )
+ endif
+ endif
+ 2dup = until
+ dup a-length = if
+ drop false
+ else
+ cells a-addr + @ key =
+ endif ;
+
+\ Create a new array, one cell in length, initialized the provided value
+: new-array { value -- array }
+ cell allocate throw value over ! ;
+
+\ Resize a heap-allocated array to be one cell longer, inserting value
+\ at idx, and shifting the tail of the array as necessary. Returns the
+\ (possibly new) array address
+: array-insert { old-array-length old-array idx value -- array }
+ old-array old-array-length 1+ cells resize throw
+ { a }
+ a idx cells + dup cell+ old-array-length idx - cells cmove>
+ value a idx cells + !
+ a
+ ;
+
+
+\ === deftype* -- protocol-enabled structs === /
+\ Each type has MalTypeType% struct allocated on the stack, with
+\ mutable fields pointing to all class-shared resources, specifically
+\ the data needed to allocate new instances, and the table of protocol
+\ methods that have been extended to the type.
+\ Use 'deftype*' to define a new type, and 'new' to create new
+\ instances of that type.
+
+struct
+ cell% field mal-type
+ cell% field mal-meta
+ \ cell% field ref-count \ Ha, right.
+end-struct MalType%
+
+struct
+ cell% 2 * field MalTypeType-struct
+ cell% field MalTypeType-methods
+ cell% field MalTypeType-method-keys
+ cell% field MalTypeType-method-vals
+ cell% field MalTypeType-name-addr
+ cell% field MalTypeType-name-len
+end-struct MalTypeType%
+
+: new ( MalTypeType -- obj )
+ dup MalTypeType-struct 2@ %allocate throw ( MalTypeType obj ) \ create struct
+ dup -rot mal-type ! ( obj ) \ set struct's type pointer to this type
+ nil over mal-meta !
+ ;
+
+: deftype* ( struct-align struct-len -- MalTypeType )
+ MalTypeType% %allot ( s-a s-l MalTypeType )
+ dup 2swap rot ( MalTypeType s-a s-l MalTypeType )
+ MalTypeType-struct 2! ( MalTypeType ) \ store struct info
+ dup MalTypeType-methods 0 swap ! ( MalTypeType )
+ dup MalTypeType-method-keys nil swap ! ( MalTypeType )
+ dup MalTypeType-method-vals nil swap ! ( MalTypeType )
+ dup MalTypeType-name-len 0 swap ! ( MalTypeType )
+ ;
+
+\ parse-name uses temporary space, so copy into dictionary stack:
+: parse-allot-name { -- new-str-addr str-len }
+ parse-name { str-addr str-len }
+ here { new-str-addr } str-len allot
+ str-addr new-str-addr str-len cmove
+ new-str-addr str-len ;
+
+: deftype ( struct-align struct-len R:type-name -- )
+ parse-allot-name { name-addr name-len }
+
+ \ allot and initialize type structure
+ deftype* { mt }
+ name-addr mt MalTypeType-name-addr !
+ name-len mt MalTypeType-name-len !
+ \ ." Defining " mt MalTypeType-name-addr @ mt MalTypeType-name-len @ type cr
+ mt name-addr name-len nextname 1 0 const-does> ;
+
+: type-name ( mal-type )
+ dup MalTypeType-name-addr @ ( mal-type name-addr )
+ swap MalTypeType-name-len @ ( name-addr name-len )
+ ;
+
+MalType% deftype MalDefault
+
+\ nil type and instance to support extending protocols to it
+MalType% deftype MalNil MalNil new constant mal-nil
+MalType% deftype MalTrue MalTrue new constant mal-true
+MalType% deftype MalFalse MalFalse new constant mal-false
+
+: mal-bool
+ 0= if mal-false else mal-true endif ;
+
+: not-object? ( obj -- bool )
+ dup 7 and 0 <> if
+ drop true
+ else
+ 1000000 <
+ endif ;
+
+\ === protocol methods === /
+
+struct
+ cell% field call-site/type
+ cell% field call-site/xt
+end-struct call-site%
+
+\ Used by protocol methods to find the appropriate implementation of
+\ themselves for the given object, and then execute that implementation.
+: execute-method { obj pxt call-site -- }
+ obj not-object? if
+ 0 0 obj int>str s" ' on non-object: " pxt >name name>string
+ s" Refusing to invoke protocol fn '" ...throw-str
+ endif
+ \ ." Calling '" pxt >name name>string type ." ' on " obj mal-type @ type-name type ." , cs " call-site .
+
+ obj mal-type @ ( type )
+ dup call-site call-site/type @ = if
+ \ ." hit!" cr
+ drop
+ call-site call-site/xt @
+ else
+ \ ." miss!" cr
+ dup MalTypeType-methods 2@ swap ( type methods method-keys )
+ dup 0= if \ No protocols extended to this type; check for a default
+ 2drop drop MalDefault MalTypeType-methods 2@ swap
+ endif
+
+ pxt array-find ( type idx found? )
+ dup 0= if \ No implementation found for this method; check for a default
+ 2drop drop MalDefault dup MalTypeType-methods 2@ swap
+ pxt array-find ( type idx found? )
+ endif
+ 0= if ( type idx )
+ 2drop
+ 0 0 s" '" obj mal-type @ type-name s" ' extended to type '"
+ pxt >name name>string s" No protocol fn '" ...throw-str
+ endif
+
+ cells over MalTypeType-method-vals @ + @ ( type xt )
+ swap call-site call-site/type ! ( xt )
+ dup call-site call-site/xt ! ( xt )
+ endif
+ obj swap execute ;
+
+\ Extend a type with a protocol method. This mutates the MalTypeType
+\ object that represents the MalType being extended.
+: extend-method* { type pxt ixt -- type }
+ \ ." Extend '" pxt dup . >name name>string safe-type ." ' to " type type-name safe-type ." , "
+ \ type MalTypeType-methods 2@ ( method-keys methods )
+ \ 0 ?do
+ \ dup i cells + @ >name name>string safe-type ." , "
+ \ \ dup i cells + @ .
+ \ loop
+ \ drop cr
+
+ type MalTypeType-methods 2@ swap ( methods method-keys )
+ dup 0= if \ no protocols extended to this type
+ 2drop
+ 1 type MalTypeType-methods !
+ pxt new-array type MalTypeType-method-keys !
+ ixt new-array type MalTypeType-method-vals !
+ else
+ pxt array-find { idx found? }
+ found? if \ overwrite
+ ." Warning: overwriting protocol method implementation '"
+ pxt >name name>string safe-type ." ' on " type type-name safe-type ." , " idx . found? . cr
+
+ type MalTypeType-method-vals @ idx cells + ixt !
+ else \ resize
+ type MalTypeType-methods dup @ 1+ dup rot ! ( new-count )
+ 1- dup type MalTypeType-method-keys @ idx pxt array-insert ( old-count new-array )
+ type MalTypeType-method-keys ! ( old-count )
+ type MalTypeType-method-vals @ idx ixt array-insert ( new-array )
+ type MalTypeType-method-vals !
+ endif
+ endif
+ type
+ ;
+
+
+\ Define a new protocol function. For example:
+\ def-protocol-method pr-str
+\ When called as above, defines a new word 'pr-str' and stores there its
+\ own xt (known as pxt). When a usage of pr-str is compiled, it
+\ allocates a call-site object on the heap and injects a reference to
+\ both that and the pxt into the compilation, along with a call to
+\ execute-method. Thus when pr-str runs, execute-method can check the
+\ call-site object to see if the type of the target object is the same
+\ as the last call for this site. If so, it executes the implementation
+\ immediately. Otherwise, it searches the target type's method list and
+\ if necessary MalDefault's method list. If an implementation of pxt is
+\ found, it is cached in the call-site, and then executed.
+: make-call-site { pxt -- }
+ pxt postpone literal \ transfer pxt into call site
+ call-site% %allocate throw dup postpone literal \ allocate call-site, push reference
+ \ dup ." Make cs '" pxt >name name>string type ." ' " . cr
+ 0 swap call-site/type !
+ postpone execute-method ;
+
+: def-protocol-method ( parse: name -- )
+ : latestxt postpone literal postpone make-call-site postpone ; immediate
+ ;
+
+: extend ( type -- type pxt install-xt <noname...>)
+ parse-name find-name name>int ( type pxt )
+ ['] extend-method*
+ :noname
+ ;
+
+: ;; ( type pxt <noname...> -- type )
+ [compile] ; ( type pxt install-xt ixt )
+ swap execute
+ ; immediate
+
+(
+\ These whole-protocol names are only needed for 'satisfies?':
+protocol IPrintable
+ def-protocol-method pr-str
+end-protocol
+
+MalList IPrintable extend
+ ' pr-str :noname drop s" <unprintable>" ; extend-method*
+
+ extend-method pr-str
+ drop s" <unprintable>" ;;
+end-extend
+)
+
+\ === Mal types and protocols === /
+
+def-protocol-method conj ( obj this -- this )
+def-protocol-method assoc ( k v this -- this )
+def-protocol-method dissoc ( k this -- this )
+def-protocol-method get ( not-found k this -- value )
+def-protocol-method mal= ( a b -- bool )
+def-protocol-method as-native ( obj -- )
+
+def-protocol-method to-list ( obj -- mal-list )
+def-protocol-method empty? ( obj -- mal-bool )
+def-protocol-method mal-count ( obj -- mal-int )
+def-protocol-method sequential? ( obj -- mal-bool )
+def-protocol-method get-map-hint ( obj -- hint )
+def-protocol-method set-map-hint! ( hint obj -- )
+
+
+\ Fully evalutate any Mal object:
+def-protocol-method mal-eval ( env ast -- val )
+
+\ Invoke an object, given whole env and unevaluated argument forms:
+def-protocol-method eval-invoke ( env list obj -- ... )
+
+\ Invoke a function, given parameter values
+def-protocol-method invoke ( argv argc mal-fn -- ... )
+
+
+: m= ( a b -- bool )
+ 2dup = if
+ 2drop true
+ else
+ mal=
+ endif ;
+
+
+MalType%
+ cell% field MalInt/int
+deftype MalInt
+
+: MalInt. { int -- mal-int }
+ MalInt new dup MalInt/int int swap ! ;
+
+MalInt
+ extend mal= ( other this -- bool )
+ over mal-type @ MalInt = if
+ MalInt/int @ swap MalInt/int @ =
+ else
+ 2drop 0
+ endif ;;
+
+ extend as-native ( mal-int -- int )
+ MalInt/int @ ;;
+drop
+
+
+MalType%
+ cell% field MalList/count
+ cell% field MalList/start
+deftype MalList
+
+: MalList. ( start count -- mal-list )
+ MalList new
+ swap over MalList/count ! ( start list )
+ swap over MalList/start ! ( list ) ;
+
+: here>MalList ( old-here -- mal-list )
+ here over - { bytes } ( old-here )
+ MalList new bytes ( old-here mal-list bytes )
+ allocate throw dup { target } over MalList/start ! ( old-here mal-list )
+ bytes cell / over MalList/count ! ( old-here mal-list )
+ swap target bytes cmove ( mal-list )
+ 0 bytes - allot \ pop list contents from dictionary stack
+ ;
+
+: MalList/concat ( list-of-lists )
+ dup MalList/start @ swap MalList/count @ { lists argc }
+ 0 lists argc cells + lists +do ( count )
+ i @ to-list MalList/count @ +
+ cell +loop { count }
+ count cells allocate throw { start }
+ start lists argc cells + lists +do ( target )
+ i @ to-list MalList/count @ cells 2dup i @ to-list MalList/start @ -rot ( target bytes src target bytes )
+ cmove ( target bytes )
+ + ( new-target )
+ cell +loop
+ drop start count MalList. ;
+
+MalList
+ extend to-list ;;
+ extend sequential? drop mal-true ;;
+ extend conj { elem old-list -- list }
+ old-list MalList/count @ 1+ { new-count }
+ new-count cells allocate throw { new-start }
+ elem new-start !
+ new-count 1 > if
+ old-list MalList/start @ new-start cell+ new-count 1- cells cmove
+ endif
+ new-start new-count MalList. ;;
+ extend empty? MalList/count @ 0= mal-bool ;;
+ extend mal-count MalList/count @ MalInt. ;;
+ extend mal=
+ over mal-nil = if
+ 2drop false
+ else
+ swap to-list dup 0= if
+ nip
+ else
+ 2dup MalList/count @ swap MalList/count @ over = if ( list-a list-b count )
+ -rot MalList/start @ swap MalList/start @ { start-b start-a }
+ true swap ( return-val count )
+ 0 ?do
+ start-a i cells + @
+ start-b i cells + @
+ m= if else
+ drop false leave
+ endif
+ loop
+ else
+ drop 2drop false
+ endif
+ endif
+ endif ;;
+drop
+
+MalList new 0 over MalList/count ! constant MalList/Empty
+
+: MalList/rest { list -- list }
+ list MalList/start @ cell+
+ list MalList/count @ 1-
+ MalList. ;
+
+
+MalType%
+ cell% field MalVector/list
+deftype MalVector
+
+MalVector
+ extend sequential? drop mal-true ;;
+ extend to-list
+ MalVector/list @ ;;
+ extend empty?
+ MalVector/list @
+ MalList/count @ 0= mal-bool ;;
+ extend mal-count
+ MalVector/list @
+ MalList/count @ MalInt. ;;
+ extend mal=
+ MalVector/list @ swap m= ;;
+ extend conj
+ MalVector/list @ { elem old-list }
+ old-list MalList/count @ { old-count }
+ old-count 1+ cells allocate throw { new-start }
+ elem new-start old-count cells + !
+ old-list MalList/start @ new-start old-count cells cmove
+ new-start old-count 1+ MalList.
+ MalVector new swap
+ over MalVector/list ! ;;
+drop
+
+MalType%
+ cell% field MalMap/list
+deftype MalMap
+
+MalMap new MalList/Empty over MalMap/list ! constant MalMap/Empty
+
+: MalMap/get-addr ( k map -- addr-or-nil )
+ MalMap/list @
+ dup MalList/start @
+ swap MalList/count @ { k start count }
+ true \ need to search?
+ k get-map-hint { hint-idx }
+ hint-idx -1 <> if
+ hint-idx count < if
+ hint-idx cells start + { key-addr }
+ key-addr @ k m= if
+ key-addr cell+
+ nip false
+ endif
+ endif
+ endif
+ if \ search
+ nil ( addr )
+ count cells start + start +do
+ i @ k m= if
+ drop i
+ dup start - cell / k set-map-hint!
+ cell+ leave
+ endif
+ [ 2 cells ] literal +loop
+ endif ;
+
+MalMap
+ extend conj ( kv map -- map )
+ MalMap/list @ \ get list
+ over MalList/start @ cell+ @ swap conj \ add value
+ swap MalList/start @ @ swap conj \ add key
+ MalMap new dup -rot MalMap/list ! \ put back in map
+ ;;
+ extend assoc ( k v map -- map )
+ MalMap/list @ \ get list
+ conj conj
+ MalMap new tuck MalMap/list ! \ put back in map
+ ;;
+ extend dissoc { k map -- map }
+ map MalMap/list @
+ dup MalList/start @ swap MalList/count @ { start count }
+ map \ return original if key not found
+ count 0 +do
+ start i cells + @ k mal= if
+ drop here
+ start i MalList. ,
+ start i 2 + cells + count i - 2 - MalList. ,
+ here>MalList MalList/concat
+ MalMap new dup -rot MalMap/list ! \ put back in map
+ endif
+ 2 +loop ;;
+ extend get ( not-found k map -- value )
+ MalMap/get-addr ( not-found addr-or-nil )
+ dup 0= if drop else nip @ endif ;;
+ extend empty?
+ MalMap/list @
+ MalList/count @ 0= mal-bool ;;
+ extend mal-count
+ MalMap/list @
+ MalList/count @ 2 / MalInt. ;;
+drop
+
+\ Examples of extending existing protocol methods to existing type
+MalDefault
+ extend conj ( obj this -- this )
+ nip ;;
+ extend to-list drop 0 ;;
+ extend empty? drop mal-true ;;
+ extend sequential? drop mal-false ;;
+ extend mal= = ;;
+ extend get-map-hint drop -1 ;;
+ extend set-map-hint! 2drop ;;
+drop
+
+MalNil
+ extend conj ( item nil -- mal-list )
+ drop MalList/Empty conj ;;
+ extend as-native drop nil ;;
+ extend get 2drop ;;
+ extend to-list drop MalList/Empty ;;
+ extend empty? drop mal-true ;;
+ extend mal-count drop 0 MalInt. ;;
+ extend mal= drop mal-nil = ;;
+drop
+
+MalType%
+ cell% field MalSymbol/sym-addr
+ cell% field MalSymbol/sym-len
+ cell% field MalSymbol/map-hint
+deftype MalSymbol
+
+: MalSymbol. { str-addr str-len -- mal-sym }
+ MalSymbol new { sym }
+ str-addr sym MalSymbol/sym-addr !
+ str-len sym MalSymbol/sym-len !
+ -1 sym MalSymbol/map-hint !
+ sym ;
+
+: unpack-sym ( mal-string -- addr len )
+ dup MalSymbol/sym-addr @
+ swap MalSymbol/sym-len @ ;
+
+MalSymbol
+ extend mal= ( other this -- bool )
+ over mal-type @ MalSymbol = if
+ unpack-sym rot unpack-sym str=
+ else
+ 2drop 0
+ endif ;;
+ extend get-map-hint MalSymbol/map-hint @ ;;
+ extend set-map-hint! MalSymbol/map-hint ! ;;
+ extend as-native ( this )
+ unpack-sym evaluate ;;
+drop
+
+MalType%
+ cell% field MalKeyword/str-addr
+ cell% field MalKeyword/str-len
+deftype MalKeyword
+
+: unpack-keyword ( mal-keyword -- addr len )
+ dup MalKeyword/str-addr @
+ swap MalKeyword/str-len @ ;
+
+MalKeyword
+ extend mal= ( other this -- bool )
+ over mal-type @ MalKeyword = if
+ unpack-keyword rot unpack-keyword str=
+ else
+ 2drop 0
+ endif ;;
+ ' as-native ' unpack-keyword extend-method*
+drop
+
+: MalKeyword. { str-addr str-len -- mal-keyword }
+ MalKeyword new { kw }
+ str-addr kw MalKeyword/str-addr !
+ str-len kw MalKeyword/str-len !
+ kw ;
+
+MalType%
+ cell% field MalString/str-addr
+ cell% field MalString/str-len
+deftype MalString
+
+: MalString.0 { str-addr str-len -- mal-str }
+ MalString new { str }
+ str-addr str MalString/str-addr !
+ str-len str MalString/str-len !
+ str ;
+' MalString.0 is MalString.
+
+: unpack-str ( mal-string -- addr len )
+ dup MalString/str-addr @
+ swap MalString/str-len @ ;
+
+MalString
+ extend mal= ( other this -- bool )
+ over mal-type @ MalString = if
+ unpack-str rot unpack-str str=
+ else
+ 2drop 0
+ endif ;;
+ ' as-native ' unpack-str extend-method*
+drop
+
+
+MalType%
+ cell% field MalNativeFn/xt
+deftype MalNativeFn
+
+: MalNativeFn. { xt -- mal-fn }
+ MalNativeFn new { mal-fn }
+ xt mal-fn MalNativeFn/xt !
+ mal-fn ;
+
+
+MalType%
+ cell% field MalUserFn/is-macro?
+ cell% field MalUserFn/env
+ cell% field MalUserFn/formal-args
+ cell% field MalUserFn/var-arg
+ cell% field MalUserFn/body
+deftype MalUserFn
+
+
+MalType%
+ cell% field SpecialOp/xt
+deftype SpecialOp
+
+: SpecialOp.
+ SpecialOp new swap over SpecialOp/xt ! ;
+
+MalType%
+ cell% field Atom/val
+deftype Atom
+
+: Atom. Atom new swap over Atom/val ! ;
diff --git a/go/Makefile b/go/Makefile
new file mode 100644
index 0000000..67e967b
--- /dev/null
+++ b/go/Makefile
@@ -0,0 +1,41 @@
+export GOPATH := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+
+#####################
+
+SOURCES_BASE = src/types/types.go src/readline/readline.go \
+ src/reader/reader.go src/printer/printer.go \
+ src/env/env.go src/core/core.go
+SOURCES_LISP = src/env/env.go src/core/core.go \
+ src/stepA_mal/stepA_mal.go
+SOURCES = $(SOURCES_BASE) $(word $(words $(SOURCES_LISP)),${SOURCES_LISP})
+
+#####################
+
+SRCS = step0_repl.go step1_read_print.go step2_eval.go step3_env.go \
+ step4_if_fn_do.go step5_tco.go step6_file.go step7_quote.go \
+ step8_macros.go step9_try.go stepA_mal.go
+BINS = $(SRCS:%.go=%)
+
+#####################
+
+all: $(BINS) mal
+
+mal: $(word $(words $(BINS)),$(BINS))
+ cp $< $@
+
+define dep_template
+$(1): $(SOURCES_BASE) src/$(1)/$(1).go
+ go build $$@
+endef
+
+$(foreach b,$(BINS),$(eval $(call dep_template,$(b))))
+
+clean:
+ rm -f $(BINS) mal
+
+.PHONY: stats stats-lisp
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/go/src/core/core.go b/go/src/core/core.go
new file mode 100644
index 0000000..2acca69
--- /dev/null
+++ b/go/src/core/core.go
@@ -0,0 +1,380 @@
+package core
+
+import (
+ "errors"
+ "io/ioutil"
+ "fmt"
+ "time"
+)
+
+import (
+ . "types"
+ "reader"
+ "printer"
+ "readline"
+)
+
+// Errors/Exceptions
+func throw(a []MalType) (MalType, error) {
+ return nil, MalError{a[0]}
+}
+
+
+// String functions
+
+func pr_str(a []MalType) (MalType, error) {
+ return printer.Pr_list(a, true, "", "", " "), nil
+}
+
+func str(a []MalType) (MalType, error) {
+ return printer.Pr_list(a, false, "", "", ""), nil
+}
+
+func prn(a []MalType) (MalType, error) {
+ fmt.Println(printer.Pr_list(a, true, "", "", " "))
+ return nil, nil
+}
+
+func println(a []MalType) (MalType, error) {
+ fmt.Println(printer.Pr_list(a, false, "", "", " "))
+ return nil, nil
+}
+
+func slurp(a []MalType) (MalType, error) {
+ b, e := ioutil.ReadFile(a[0].(string))
+ if e != nil { return nil, e }
+ return string(b), nil
+}
+
+// Number functions
+func time_ms(a []MalType) (MalType, error) {
+ return int(time.Now().UnixNano() / int64(time.Millisecond)), nil
+}
+
+
+// Hash Map functions
+func copy_hash_map(hm HashMap) (HashMap) {
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range hm.Val { new_hm.Val[k] = v }
+ return new_hm
+}
+
+func assoc(a []MalType) (MalType, error) {
+ if len(a) <3 { return nil, errors.New("assoc requires at least 3 arguments") }
+ if (len(a) % 2 != 1) { return nil, errors.New("assoc requires odd number of arguments") }
+ if !HashMap_Q(a[0]) { return nil, errors.New("assoc called on non-hash map") }
+ new_hm := copy_hash_map(a[0].(HashMap))
+ for i := 1; i < len(a); i+=2 {
+ key := a[i]
+ if !String_Q(key) { return nil, errors.New("assoc called with non-string key") }
+ new_hm.Val[key.(string)] = a[i+1]
+ }
+ return new_hm, nil
+}
+
+func dissoc(a []MalType) (MalType, error) {
+ if len(a) <2 { return nil, errors.New("dissoc requires at least 3 arguments") }
+ if !HashMap_Q(a[0]) { return nil, errors.New("dissoc called on non-hash map") }
+ new_hm := copy_hash_map(a[0].(HashMap))
+ for i := 1; i < len(a); i+=1 {
+ key := a[i]
+ if !String_Q(key) { return nil, errors.New("dissoc called with non-string key") }
+ delete(new_hm.Val,key.(string))
+ }
+ return new_hm, nil
+}
+
+func get(a []MalType) (MalType, error) {
+ if len(a) != 2 { return nil, errors.New("get requires 2 arguments") }
+ if Nil_Q(a[0]) { return nil, nil }
+ if !HashMap_Q(a[0]) { return nil, errors.New("get called on non-hash map") }
+ if !String_Q(a[1]) { return nil, errors.New("get called with non-string key") }
+ return a[0].(HashMap).Val[a[1].(string)], nil
+}
+
+func contains_Q(hm MalType, key MalType) (MalType, error) {
+ if Nil_Q(hm) { return false, nil }
+ if !HashMap_Q(hm) { return nil, errors.New("get called on non-hash map") }
+ if !String_Q(key) { return nil, errors.New("get called with non-string key") }
+ _, ok := hm.(HashMap).Val[key.(string)]
+ return ok, nil
+}
+
+func keys(a []MalType) (MalType, error) {
+ if !HashMap_Q(a[0]) { return nil, errors.New("keys called on non-hash map") }
+ slc := []MalType{}
+ for k, _ := range a[0].(HashMap).Val {
+ slc = append(slc, k)
+ }
+ return List{slc,nil}, nil
+}
+func vals(a []MalType) (MalType, error) {
+ if !HashMap_Q(a[0]) { return nil, errors.New("keys called on non-hash map") }
+ slc := []MalType{}
+ for _, v := range a[0].(HashMap).Val {
+ slc = append(slc, v)
+ }
+ return List{slc,nil}, nil
+}
+
+
+// Sequence functions
+
+func cons(a []MalType) (MalType, error) {
+ val := a[0]
+ lst, e := GetSlice(a[1]); if e != nil { return nil, e }
+
+ return List{append([]MalType{val}, lst...),nil}, nil
+}
+
+func concat(a []MalType) (MalType, error) {
+ if len(a) == 0 { return List{}, nil }
+ slc1, e := GetSlice(a[0]); if e != nil { return nil, e }
+ for i := 1; i < len(a); i+=1 {
+ slc2, e := GetSlice(a[i]); if e != nil { return nil, e }
+ slc1 = append(slc1, slc2...)
+ }
+ return List{slc1,nil}, nil
+}
+
+func nth(a []MalType) (MalType, error) {
+ slc, e := GetSlice(a[0]); if e != nil { return nil, e }
+ idx := a[1].(int)
+ if idx < len(slc) {
+ return slc[idx], nil
+ } else {
+ return nil, errors.New("nth: index out of range")
+ }
+}
+
+func first(a []MalType) (MalType, error) {
+ if len(a) == 0 { return nil, nil }
+ slc, e := GetSlice(a[0]); if e != nil { return nil, e }
+ if len(slc) == 0 { return nil, nil }
+ return slc[0], nil
+}
+
+func rest(a []MalType) (MalType, error) {
+ slc, e := GetSlice(a[0]); if e != nil { return nil, e }
+ if len(slc) == 0 { return List{}, nil }
+ return List{slc[1:],nil}, nil
+}
+
+
+func empty_Q(a []MalType) (MalType, error) {
+ switch obj := a[0].(type) {
+ case List: return len(obj.Val) == 0, nil
+ case Vector: return len(obj.Val) == 0, nil
+ case nil: return true, nil
+ default: return nil, errors.New("Count called on non-sequence")
+ }
+}
+
+func count(a []MalType) (MalType, error) {
+ switch obj := a[0].(type) {
+ case List: return len(obj.Val), nil
+ case Vector: return len(obj.Val), nil
+ case map[string]MalType: return len(obj), nil
+ case nil: return 0, nil
+ default: return nil, errors.New("Count called on non-sequence")
+ }
+}
+
+func apply(a []MalType) (MalType, error) {
+ if len(a) < 2 { return nil, errors.New("apply requires at least 2 args") }
+ f := a[0]
+ args := []MalType{}
+ for _, b := range a[1:len(a)-1] {
+ args = append(args, b)
+ }
+ last, e := GetSlice(a[len(a)-1]); if e != nil { return nil, e }
+ args = append(args, last...)
+ return Apply(f, args)
+}
+
+func do_map(a []MalType) (MalType, error) {
+ if len(a) != 2 { return nil, errors.New("map requires 2 args") }
+ f := a[0]
+ results := []MalType{}
+ args, e := GetSlice(a[1]); if e != nil { return nil, e }
+ for _, arg := range args {
+ res, e := Apply(f, []MalType{arg})
+ results = append(results, res)
+ if e != nil { return nil, e }
+ }
+ return List{results,nil}, nil
+}
+
+func conj(a []MalType) (MalType, error) {
+ if len(a) <2 { return nil, errors.New("conj requires at least 2 arguments") }
+ switch seq := a[0].(type) {
+ case List:
+ new_slc := []MalType{}
+ for i := len(a)-1 ; i > 0 ; i-=1 {
+ new_slc = append(new_slc, a[i])
+ }
+ return List{append(new_slc, seq.Val...),nil}, nil
+ case Vector:
+ new_slc := seq.Val
+ for _, x := range a[1:] {
+ new_slc = append(new_slc, x)
+ }
+ return Vector{new_slc,nil}, nil
+ }
+
+ if !HashMap_Q(a[0]) { return nil, errors.New("dissoc called on non-hash map") }
+ new_hm := copy_hash_map(a[0].(HashMap))
+ for i := 1; i < len(a); i+=1 {
+ key := a[i]
+ if !String_Q(key) { return nil, errors.New("dissoc called with non-string key") }
+ delete(new_hm.Val,key.(string))
+ }
+ return new_hm, nil
+}
+
+
+
+// Metadata functions
+func with_meta(a []MalType) (MalType, error) {
+ if len(a) != 2 { return nil, errors.New("with-meta requires 2 args") }
+ obj := a[0]; m := a[1]
+ switch tobj := obj.(type) {
+ case List: return List{tobj.Val,m}, nil
+ case Vector: return Vector{tobj.Val,m}, nil
+ case HashMap: return HashMap{tobj.Val,m}, nil
+ case Func: return Func{tobj.Fn,m}, nil
+ case MalFunc: fn := tobj; fn.Meta = m; return fn, nil
+ default: return nil, errors.New("with-meta not supported on type")
+ }
+}
+
+func meta(a []MalType) (MalType, error) {
+ obj := a[0]
+ switch tobj := obj.(type) {
+ case List: return tobj.Meta, nil
+ case Vector: return tobj.Meta, nil
+ case HashMap: return tobj.Meta, nil
+ case Func: return tobj.Meta, nil
+ case MalFunc: return tobj.Meta, nil
+ default: return nil, errors.New("meta not supported on type")
+ }
+}
+
+
+// Atom functions
+func deref(a []MalType) (MalType, error) {
+ if !Atom_Q(a[0]) { return nil, errors.New("deref called with non-atom") }
+ return a[0].(*Atom).Val, nil
+}
+
+func reset_BANG(a []MalType) (MalType, error) {
+ if !Atom_Q(a[0]) { return nil, errors.New("reset! called with non-atom") }
+ a[0].(*Atom).Set(a[1])
+ return a[1], nil
+}
+
+func swap_BANG(a []MalType) (MalType, error) {
+ if !Atom_Q(a[0]) { return nil, errors.New("swap! called with non-atom") }
+ if len(a) < 2 { return nil, errors.New("swap! requires at least 2 args") }
+ atm := a[0].(*Atom)
+ args := []MalType{atm.Val}
+ f := a[1]
+ args = append(args, a[2:]...)
+ res, e := Apply(f, args)
+ if e != nil { return nil, e }
+ atm.Set(res)
+ return res, nil
+}
+
+
+// core namespace
+var NS = map[string]MalType{
+ "=": func(a []MalType) (MalType, error) {
+ return Equal_Q(a[0], a[1]), nil },
+ "throw": throw,
+ "nil?": func(a []MalType) (MalType, error) {
+ return Nil_Q(a[0]), nil },
+ "true?": func(a []MalType) (MalType, error) {
+ return True_Q(a[0]), nil },
+ "false?": func(a []MalType) (MalType, error) {
+ return False_Q(a[0]), nil },
+ "symbol": func(a []MalType) (MalType, error) {
+ return Symbol{a[0].(string)}, nil },
+ "symbol?": func(a []MalType) (MalType, error) {
+ return Symbol_Q(a[0]), nil },
+ "keyword": func(a []MalType) (MalType, error) {
+ return NewKeyword(a[0].(string)) },
+ "keyword?": func(a []MalType) (MalType, error) {
+ return Keyword_Q(a[0]), nil },
+
+ "pr-str": func(a []MalType) (MalType, error) { return pr_str(a) },
+ "str": func(a []MalType) (MalType, error) { return str(a) },
+ "prn": func(a []MalType) (MalType, error) { return prn(a) },
+ "println": func(a []MalType) (MalType, error) { return println(a) },
+ "read-string": func(a []MalType) (MalType, error) {
+ return reader.Read_str(a[0].(string)) },
+ "slurp": slurp,
+ "readline": func(a []MalType) (MalType, error) {
+ return readline.Readline(a[0].(string)) },
+
+ "<": func(a []MalType) (MalType, error) {
+ return a[0].(int) < a[1].(int), nil },
+ "<=": func(a []MalType) (MalType, error) {
+ return a[0].(int) <= a[1].(int), nil },
+ ">": func(a []MalType) (MalType, error) {
+ return a[0].(int) > a[1].(int), nil },
+ ">=": func(a []MalType) (MalType, error) {
+ return a[0].(int) >= a[1].(int), nil },
+ "+": func(a []MalType) (MalType, error) {
+ return a[0].(int) + a[1].(int), nil },
+ "-": func(a []MalType) (MalType, error) {
+ return a[0].(int) - a[1].(int), nil },
+ "*": func(a []MalType) (MalType, error) {
+ return a[0].(int) * a[1].(int), nil },
+ "/": func(a []MalType) (MalType, error) {
+ return a[0].(int) / a[1].(int), nil },
+ "time-ms": time_ms,
+
+ "list": func(a []MalType) (MalType, error) {
+ return List{a,nil}, nil },
+ "list?": func(a []MalType) (MalType, error) {
+ return List_Q(a[0]), nil },
+ "vector": func(a []MalType) (MalType, error) {
+ return Vector{a,nil}, nil },
+ "vector?": func(a []MalType) (MalType, error) {
+ return Vector_Q(a[0]), nil },
+ "hash-map": func(a []MalType) (MalType, error) {
+ return NewHashMap(List{a,nil}) },
+ "map?": func(a []MalType) (MalType, error) {
+ return HashMap_Q(a[0]), nil },
+ "assoc": assoc,
+ "dissoc": dissoc,
+ "get": get,
+ "contains?": func(a []MalType) (MalType, error) {
+ return contains_Q(a[0], a[1]) },
+ "keys": keys,
+ "vals": vals,
+
+ "sequential?": func(a []MalType) (MalType, error) {
+ return Sequential_Q(a[0]), nil },
+ "cons": cons,
+ "concat": concat,
+ "nth": nth,
+ "first": first,
+ "rest": rest,
+ "empty?": empty_Q,
+ "count": count,
+ "apply": apply,
+ "map": do_map,
+ "conj": conj,
+
+ "with-meta": with_meta,
+ "meta": meta,
+ "atom": func(a []MalType) (MalType, error) {
+ return &Atom{a[0],nil}, nil },
+ "atom?": func(a []MalType) (MalType, error) {
+ return Atom_Q(a[0]), nil },
+ "deref": deref,
+ "reset!": reset_BANG,
+ "swap!": swap_BANG,
+ }
diff --git a/go/src/env/env.go b/go/src/env/env.go
new file mode 100644
index 0000000..584c9a7
--- /dev/null
+++ b/go/src/env/env.go
@@ -0,0 +1,59 @@
+package env
+
+import (
+ "errors"
+ //"fmt"
+)
+
+import (
+ . "types"
+)
+
+type Env struct {
+ data map[string]MalType
+ outer EnvType
+}
+
+func NewEnv(outer EnvType, binds_mt MalType, exprs_mt MalType) (EnvType, error) {
+ env := Env{map[string]MalType{}, outer}
+
+ if binds_mt != nil && exprs_mt != nil {
+ binds, e := GetSlice(binds_mt); if e != nil { return nil, e }
+ exprs, e := GetSlice(exprs_mt); if e != nil { return nil, e }
+ // Return a new Env with symbols in binds boudn to
+ // corresponding values in exprs
+ for i := 0; i < len(binds); i+=1 {
+ if (Symbol_Q(binds[i]) && binds[i].(Symbol).Val == "&") {
+ env.data[binds[i+1].(Symbol).Val] = List{exprs[i:],nil}
+ break
+ } else {
+ env.data[binds[i].(Symbol).Val] = exprs[i]
+ }
+ }
+ }
+ //return &et, nil
+ return env, nil
+}
+
+func (e Env) Find(key Symbol) EnvType {
+ if _, ok := e.data[key.Val]; ok {
+ return e
+ } else if (e.outer != nil) {
+ return e.outer.Find(key)
+ } else {
+ return nil
+ }
+}
+
+func (e Env) Set(key Symbol, value MalType) MalType {
+ e.data[key.Val] = value
+ return value
+}
+
+func (e Env) Get(key Symbol) (MalType, error) {
+ env := e.Find(key)
+ if env == nil {
+ return nil, errors.New("'" + key.Val + "' not found")
+ }
+ return env.(Env).data[key.Val], nil
+}
diff --git a/go/src/printer/printer.go b/go/src/printer/printer.go
new file mode 100644
index 0000000..1b8e9fe
--- /dev/null
+++ b/go/src/printer/printer.go
@@ -0,0 +1,62 @@
+package printer
+
+import (
+ "fmt"
+ "strings"
+)
+
+import (
+ "types"
+)
+
+func Pr_list(lst []types.MalType, pr bool,
+ start string, end string, join string) string {
+ str_list := make([]string, 0, len(lst))
+ for _, e := range lst {
+ str_list = append(str_list, Pr_str(e, pr))
+ }
+ return start + strings.Join(str_list, join) + end
+}
+
+func Pr_str(obj types.MalType, print_readably bool) string {
+ switch tobj := obj.(type) {
+ case types.List:
+ return Pr_list(tobj.Val, print_readably, "(", ")", " ")
+ case types.Vector:
+ return Pr_list(tobj.Val, print_readably, "[", "]", " ")
+ case types.HashMap:
+ str_list := make([]string, 0, len(tobj.Val)*2)
+ for k, v := range tobj.Val {
+ str_list = append(str_list, Pr_str(k, print_readably))
+ str_list = append(str_list, Pr_str(v, print_readably))
+ }
+ return "{" + strings.Join(str_list, " ") + "}"
+ case string:
+ if strings.HasPrefix(tobj, "\u029e") {
+ return ":" + tobj[2:len(tobj)]
+ } else if print_readably {
+ return `"` + strings.Replace(
+ strings.Replace(
+ strings.Replace(tobj,`\`,`\\`, -1),
+ `"`, `\"`, -1),
+ "\n", `\n`, -1) + `"`
+ } else {
+ return tobj
+ }
+ case types.Symbol:
+ return tobj.Val
+ case nil:
+ return "nil"
+ case types.MalFunc:
+ return "(fn* " +
+ Pr_str(tobj.Params, true) + " " +
+ Pr_str(tobj.Exp, true) + ")"
+ case func([]types.MalType)(types.MalType, error):
+ return fmt.Sprintf("<function %v>", obj)
+ case *types.Atom:
+ return "(atom " +
+ Pr_str(tobj.Val, true) + ")"
+ default:
+ return fmt.Sprintf("%v", obj)
+ }
+}
diff --git a/go/src/reader/reader.go b/go/src/reader/reader.go
new file mode 100644
index 0000000..9cacb8b
--- /dev/null
+++ b/go/src/reader/reader.go
@@ -0,0 +1,162 @@
+package reader
+
+import (
+ "errors"
+ "regexp"
+ "strconv"
+ "strings"
+ //"fmt"
+)
+
+import (
+ . "types"
+)
+
+type Reader interface {
+ next() *string
+ peek() *string
+}
+
+type TokenReader struct {
+ tokens []string
+ position int
+}
+
+func (tr *TokenReader) next() *string {
+ if tr.position >= len(tr.tokens) { return nil }
+ token := tr.tokens[tr.position]
+ tr.position = tr.position + 1
+ return &token
+}
+
+func (tr *TokenReader) peek() *string {
+ if tr.position >= len(tr.tokens) { return nil }
+ return &tr.tokens[tr.position]
+}
+
+
+
+func tokenize (str string) []string {
+ results := make([]string, 0, 1)
+ // Work around lack of quoting in backtick
+ re := regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'` + "`" +
+ `~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` + "`" +
+ `,;)]*)`)
+ for _, group := range re.FindAllStringSubmatch(str, -1) {
+ if (group[1] == "") || (group[1][0] == ';') { continue }
+ results = append(results, group[1])
+ }
+ return results
+}
+
+func read_atom(rdr Reader) (MalType, error) {
+ token := rdr.next()
+ if token == nil { return nil, errors.New("read_atom underflow") }
+ if match, _ := regexp.MatchString(`^-?[0-9]+$`, *token); match {
+ var i int
+ var e error
+ if i, e = strconv.Atoi(*token); e != nil {
+ return nil, errors.New("number parse error")
+ }
+ return i, nil
+ } else if (*token)[0] == '"' {
+ str := (*token)[1:len(*token)-1]
+ return strings.Replace(
+ strings.Replace(str, `\"`, `"`, -1),
+ `\n`, "\n", -1), nil
+ } else if (*token)[0] == ':' {
+ return NewKeyword((*token)[1:len(*token)])
+ } else if *token == "nil" {
+ return nil, nil
+ } else if *token == "true" {
+ return true, nil
+ } else if *token == "false" {
+ return false, nil
+ } else {
+ return Symbol{*token}, nil
+ }
+ return token, nil
+}
+
+func read_list(rdr Reader, start string, end string) (MalType, error) {
+ token := rdr.next()
+ if token == nil { return nil, errors.New("read_list underflow") }
+ if *token != start {
+ return nil, errors.New("expected '" + start + "'")
+ }
+
+ ast_list := []MalType{}
+ token = rdr.peek()
+ for ; true ; token = rdr.peek() {
+ if token == nil { return nil, errors.New("exepected '" + end + "', got EOF") }
+ if *token == end { break }
+ f, e := read_form(rdr)
+ if e != nil { return nil, e }
+ ast_list = append(ast_list, f)
+ }
+ rdr.next()
+ return List{ast_list,nil}, nil
+}
+
+func read_vector(rdr Reader) (MalType, error) {
+ lst, e := read_list(rdr, "[", "]")
+ if e != nil { return nil, e }
+ vec := Vector{lst.(List).Val,nil}
+ return vec, nil
+}
+
+func read_hash_map(rdr Reader) (MalType, error) {
+ mal_lst, e := read_list(rdr, "{", "}")
+ if e != nil { return nil, e }
+ return NewHashMap(mal_lst)
+}
+
+func read_form(rdr Reader) (MalType, error) {
+ token := rdr.peek()
+ if token == nil { return nil, errors.New("read_form underflow") }
+ switch (*token) {
+
+ case `'`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"quote"}, form},nil}, nil
+ case "`": rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"quasiquote"}, form},nil}, nil
+ case `~`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"unquote"}, form},nil}, nil
+ case `~@`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"splice-unquote"}, form},nil}, nil
+ case `^`: rdr.next();
+ meta, e := read_form(rdr); if e != nil { return nil, e }
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"with-meta"}, form,meta},nil}, nil
+ case `@`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"deref"}, form},nil}, nil
+
+ // list
+ case ")": return nil, errors.New("unexpected ')'")
+ case "(": return read_list(rdr, "(", ")")
+
+ // vector
+ case "]": return nil, errors.New("unexpected ']'")
+ case "[": return read_vector(rdr)
+
+ // hash-map
+ case "}": return nil, errors.New("unexpected '}'")
+ case "{": return read_hash_map(rdr)
+ default: return read_atom(rdr)
+ }
+ return read_atom(rdr)
+}
+
+func Read_str(str string) (MalType, error) {
+ var tokens = tokenize(str);
+ if len(tokens) == 0 {
+ return nil, errors.New("<empty line>")
+ }
+
+ return read_form(&TokenReader{tokens: tokens, position: 0})
+}
diff --git a/go/src/readline/readline.go b/go/src/readline/readline.go
new file mode 100644
index 0000000..2777cb4
--- /dev/null
+++ b/go/src/readline/readline.go
@@ -0,0 +1,70 @@
+package readline
+
+/*
+// IMPORTANT: choose one
+#cgo LDFLAGS: -ledit
+//#cgo LDFLAGS: -lreadline // NOTE: libreadline is GPL
+
+// free()
+#include <stdlib.h>
+// readline()
+#include <readline/readline.h>
+// add_history()
+#include <readline/history.h>
+*/
+import "C"
+
+import (
+ "errors"
+ "unsafe"
+ "strings"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "fmt"
+)
+
+var HISTORY_FILE = ".mal-history"
+
+var rl_history_loaded = false
+
+func Readline(prompt string) (string, error) {
+ history_path := filepath.Join(os.Getenv("HOME"), "/", HISTORY_FILE)
+
+ if !rl_history_loaded {
+ rl_history_loaded = true
+ content, e := ioutil.ReadFile(history_path)
+ if e != nil { return "", e }
+
+ for _, add_line := range strings.Split(string(content), "\n") {
+ if add_line == "" { continue }
+ c_add_line := C.CString(add_line)
+ C.add_history(c_add_line)
+ C.free(unsafe.Pointer(c_add_line))
+ }
+ }
+
+
+ c_prompt := C.CString(prompt)
+ defer C.free(unsafe.Pointer(c_prompt))
+
+ c_line := C.readline(c_prompt)
+ defer C.free(unsafe.Pointer(c_line))
+ line := C.GoString(c_line)
+
+ if c_line == nil {
+ return "", errors.New("C.readline call failed")
+ }
+ C.add_history(c_line)
+
+ // append to file
+ f, e := os.OpenFile(history_path, os.O_APPEND|os.O_WRONLY, 0600)
+ if e == nil {
+ defer f.Close()
+
+ _, e = f.WriteString(line+"\n")
+ if e != nil { fmt.Printf("error writing to history") }
+ }
+
+ return line, nil
+}
diff --git a/go/src/step0_repl/step0_repl.go b/go/src/step0_repl/step0_repl.go
new file mode 100644
index 0000000..2e4c47c
--- /dev/null
+++ b/go/src/step0_repl/step0_repl.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+)
+
+import (
+ "readline"
+)
+
+// read
+func READ(str string) string {
+ return str
+}
+
+// eval
+func EVAL(ast string, env string) string {
+ return ast
+}
+
+// print
+func PRINT(exp string) string {
+ return exp
+}
+
+// repl
+func rep(str string) string {
+ return PRINT(EVAL(READ(str), ""))
+}
+
+func main() {
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ fmt.Println(rep(text))
+ }
+}
diff --git a/go/src/step1_read_print/step1_read_print.go b/go/src/step1_read_print/step1_read_print.go
new file mode 100644
index 0000000..e008ed5
--- /dev/null
+++ b/go/src/step1_read_print/step1_read_print.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func EVAL(ast MalType, env string) (MalType, error) {
+ return ast, nil
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, ""); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step2_eval/step2_eval.go b/go/src/step2_eval/step2_eval.go
new file mode 100644
index 0000000..7a9a33f
--- /dev/null
+++ b/go/src/step2_eval/step2_eval.go
@@ -0,0 +1,127 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func eval_ast(ast MalType, env map[string]MalType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ k := ast.(Symbol).Val
+ exp, ok := env[k]
+ if !ok { return nil, errors.New("'" + k + "' not found") }
+ return exp, nil
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env map[string]MalType) (MalType, error) {
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f, ok := el.(List).Val[0].(func([]MalType)(MalType, error))
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return f(el.(List).Val[1:])
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+var repl_env = map[string]MalType{
+ "+": func(a []MalType) (MalType, error) {
+ return a[0].(int) + a[1].(int), nil
+ },
+ "-": func(a []MalType) (MalType, error) {
+ return a[0].(int) - a[1].(int), nil
+ },
+ "*": func(a []MalType) (MalType, error) {
+ return a[0].(int) * a[1].(int), nil
+ },
+ "/": func(a []MalType) (MalType, error) {
+ return a[0].(int) / a[1].(int), nil
+ },
+}
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step3_env/step3_env.go b/go/src/step3_env/step3_env.go
new file mode 100644
index 0000000..0c45bf2
--- /dev/null
+++ b/go/src/step3_env/step3_env.go
@@ -0,0 +1,155 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ return EVAL(a2, let_env)
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f, ok := el.(List).Val[0].(func([]MalType)(MalType, error))
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return f(el.(List).Val[1:])
+ }
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ repl_env.Set(Symbol{"+"}, func(a []MalType) (MalType, error) {
+ return a[0].(int) + a[1].(int), nil })
+ repl_env.Set(Symbol{"-"}, func(a []MalType) (MalType, error) {
+ return a[0].(int) - a[1].(int), nil })
+ repl_env.Set(Symbol{"*"}, func(a []MalType) (MalType, error) {
+ return a[0].(int) * a[1].(int), nil })
+ repl_env.Set(Symbol{"/"}, func(a []MalType) (MalType, error) {
+ return a[0].(int) / a[1].(int), nil })
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step4_if_fn_do/step4_if_fn_do.go b/go/src/step4_if_fn_do/step4_if_fn_do.go
new file mode 100644
index 0000000..d90a720
--- /dev/null
+++ b/go/src/step4_if_fn_do/step4_if_fn_do.go
@@ -0,0 +1,179 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ return EVAL(a2, let_env)
+ case "do":
+ el, e := eval_ast(List{ast.(List).Val[1:],nil}, env)
+ if e != nil { return nil, e }
+ lst := el.(List).Val
+ if len(lst) == 0 { return nil, nil }
+ return lst[len(lst)-1], nil
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ return EVAL(ast.(List).Val[3], env)
+ } else {
+ return nil, nil
+ }
+ } else {
+ return EVAL(a2, env)
+ }
+ case "fn*":
+ return func(arguments []MalType) (MalType, error) {
+ new_env, e := NewEnv(env, a1, List{arguments,nil})
+ if e != nil { return nil, e }
+ return EVAL(a2, new_env)
+ }, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f, ok := el.(List).Val[0].(func([]MalType)(MalType, error))
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return f(el.(List).Val[1:])
+ }
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, v)
+ }
+
+ // core.mal: defined using the language itself
+ rep("(def! not (fn* (a) (if a false true)))")
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step5_tco/step5_tco.go b/go/src/step5_tco/step5_tco.go
new file mode 100644
index 0000000..8c6ff37
--- /dev/null
+++ b/go/src/step5_tco/step5_tco.go
@@ -0,0 +1,189 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ for {
+
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ ast = a2
+ env = let_env
+ case "do":
+ lst := ast.(List).Val
+ _, e := eval_ast(List{lst[1:len(lst)-1],nil}, env)
+ if e != nil { return nil, e }
+ if len(lst) == 1 { return nil, nil }
+ ast = lst[len(lst)-1]
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ ast = ast.(List).Val[3]
+ } else {
+ return nil, nil
+ }
+ } else {
+ ast = a2
+ }
+ case "fn*":
+ fn := MalFunc{EVAL, a2, env, a1, false, NewEnv, nil}
+ return fn, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f := el.(List).Val[0]
+ if MalFunc_Q(f) {
+ fn := f.(MalFunc)
+ ast = fn.Exp
+ env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
+ if e != nil { return nil, e }
+ } else {
+ fn, ok := f.(Func)
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return fn.Fn(el.(List).Val[1:])
+ }
+ }
+
+ } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, Func{v.(func([]MalType)(MalType,error)),nil})
+ }
+
+ // core.mal: defined using the language itself
+ rep("(def! not (fn* (a) (if a false true)))")
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step6_file/step6_file.go b/go/src/step6_file/step6_file.go
new file mode 100644
index 0000000..701ac47
--- /dev/null
+++ b/go/src/step6_file/step6_file.go
@@ -0,0 +1,208 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+ "os"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ for {
+
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ ast = a2
+ env = let_env
+ case "do":
+ lst := ast.(List).Val
+ _, e := eval_ast(List{lst[1:len(lst)-1],nil}, env)
+ if e != nil { return nil, e }
+ if len(lst) == 1 { return nil, nil }
+ ast = lst[len(lst)-1]
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ ast = ast.(List).Val[3]
+ } else {
+ return nil, nil
+ }
+ } else {
+ ast = a2
+ }
+ case "fn*":
+ fn := MalFunc{EVAL, a2, env, a1, false, NewEnv, nil}
+ return fn, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f := el.(List).Val[0]
+ if MalFunc_Q(f) {
+ fn := f.(MalFunc)
+ ast = fn.Exp
+ env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
+ if e != nil { return nil, e }
+ } else {
+ fn, ok := f.(Func)
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return fn.Fn(el.(List).Val[1:])
+ }
+ }
+
+ } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, Func{v.(func([]MalType)(MalType,error)),nil})
+ }
+ repl_env.Set(Symbol{"eval"}, Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
+ repl_env.Set(Symbol{"*ARGV*"}, List{})
+
+ // 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) \")\")))))")
+
+ // called with mal script to load and eval
+ if len(os.Args) > 1 {
+ args := make([]MalType, 0, len(os.Args)-2)
+ for _,a := range os.Args[2:] {
+ args = append(args, a)
+ }
+ repl_env.Set(Symbol{"*ARGV*"}, List{args,nil})
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step7_quote/step7_quote.go b/go/src/step7_quote/step7_quote.go
new file mode 100644
index 0000000..d93f620
--- /dev/null
+++ b/go/src/step7_quote/step7_quote.go
@@ -0,0 +1,241 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+ "os"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func is_pair(x MalType) bool {
+ slc, e := GetSlice(x)
+ if e != nil { return false }
+ return len(slc) > 0
+}
+
+func quasiquote(ast MalType) MalType {
+ if !is_pair(ast) {
+ return List{[]MalType{Symbol{"quote"}, ast},nil}
+ } else {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && (a0.(Symbol).Val == "unquote") {
+ return slc[1]
+ } else if is_pair(a0) {
+ slc0, _ := GetSlice(a0)
+ a00 := slc0[0]
+ if Symbol_Q(a00) && (a00.(Symbol).Val == "splice-unquote") {
+ return List{[]MalType{Symbol{"concat"},
+ slc0[1],
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+ }
+ return List{[]MalType{Symbol{"cons"},
+ quasiquote(a0),
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+}
+
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ for {
+
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ ast = a2
+ env = let_env
+ case "quote":
+ return a1, nil
+ case "quasiquote":
+ ast = quasiquote(a1)
+ case "do":
+ lst := ast.(List).Val
+ _, e := eval_ast(List{lst[1:len(lst)-1],nil}, env)
+ if e != nil { return nil, e }
+ if len(lst) == 1 { return nil, nil }
+ ast = lst[len(lst)-1]
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ ast = ast.(List).Val[3]
+ } else {
+ return nil, nil
+ }
+ } else {
+ ast = a2
+ }
+ case "fn*":
+ fn := MalFunc{EVAL, a2, env, a1, false, NewEnv, nil}
+ return fn, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f := el.(List).Val[0]
+ if MalFunc_Q(f) {
+ fn := f.(MalFunc)
+ ast = fn.Exp
+ env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
+ if e != nil { return nil, e }
+ } else {
+ fn, ok := f.(Func)
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return fn.Fn(el.(List).Val[1:])
+ }
+ }
+
+ } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, Func{v.(func([]MalType)(MalType,error)),nil})
+ }
+ repl_env.Set(Symbol{"eval"}, Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
+ repl_env.Set(Symbol{"*ARGV*"}, List{})
+
+ // 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) \")\")))))")
+
+ // called with mal script to load and eval
+ if len(os.Args) > 1 {
+ args := make([]MalType, 0, len(os.Args)-2)
+ for _,a := range os.Args[2:] {
+ args = append(args, a)
+ }
+ repl_env.Set(Symbol{"*ARGV*"}, List{args,nil})
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step8_macros/step8_macros.go b/go/src/step8_macros/step8_macros.go
new file mode 100644
index 0000000..86db33b
--- /dev/null
+++ b/go/src/step8_macros/step8_macros.go
@@ -0,0 +1,282 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+ "os"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func is_pair(x MalType) bool {
+ slc, e := GetSlice(x)
+ if e != nil { return false }
+ return len(slc) > 0
+}
+
+func quasiquote(ast MalType) MalType {
+ if !is_pair(ast) {
+ return List{[]MalType{Symbol{"quote"}, ast},nil}
+ } else {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && (a0.(Symbol).Val == "unquote") {
+ return slc[1]
+ } else if is_pair(a0) {
+ slc0, _ := GetSlice(a0)
+ a00 := slc0[0]
+ if Symbol_Q(a00) && (a00.(Symbol).Val == "splice-unquote") {
+ return List{[]MalType{Symbol{"concat"},
+ slc0[1],
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+ }
+ return List{[]MalType{Symbol{"cons"},
+ quasiquote(a0),
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+}
+
+func is_macro_call(ast MalType, env EnvType) bool {
+ if List_Q(ast) {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && env.Find(a0.(Symbol)) != nil {
+ mac, e := env.Get(a0.(Symbol))
+ if e != nil { return false }
+ if MalFunc_Q(mac) {
+ return mac.(MalFunc).GetMacro()
+ }
+ }
+ }
+ return false
+}
+
+func macroexpand(ast MalType, env EnvType) (MalType, error) {
+ var mac MalType
+ var e error
+ for ; is_macro_call(ast, env) ; {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ mac, e = env.Get(a0.(Symbol)); if e != nil { return nil, e }
+ fn := mac.(MalFunc)
+ ast, e = Apply(fn, slc[1:]); if e != nil { return nil, e }
+ }
+ return ast, nil
+}
+
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ var e error
+ for {
+
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ ast, e = macroexpand(ast, env); if e != nil { return nil, e }
+ if (!List_Q(ast)) { return ast, nil }
+
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ ast = a2
+ env = let_env
+ case "quote":
+ return a1, nil
+ case "quasiquote":
+ ast = quasiquote(a1)
+ case "defmacro!":
+ fn, e := EVAL(a2, env)
+ fn = fn.(MalFunc).SetMacro()
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), fn), nil
+ case "macroexpand":
+ return macroexpand(a1, env)
+ case "do":
+ lst := ast.(List).Val
+ _, e := eval_ast(List{lst[1:len(lst)-1],nil}, env)
+ if e != nil { return nil, e }
+ if len(lst) == 1 { return nil, nil }
+ ast = lst[len(lst)-1]
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ ast = ast.(List).Val[3]
+ } else {
+ return nil, nil
+ }
+ } else {
+ ast = a2
+ }
+ case "fn*":
+ fn := MalFunc{EVAL, a2, env, a1, false, NewEnv, nil}
+ return fn, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f := el.(List).Val[0]
+ if MalFunc_Q(f) {
+ fn := f.(MalFunc)
+ ast = fn.Exp
+ env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
+ if e != nil { return nil, e }
+ } else {
+ fn, ok := f.(Func)
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return fn.Fn(el.(List).Val[1:])
+ }
+ }
+
+ } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, Func{v.(func([]MalType)(MalType,error)),nil})
+ }
+ repl_env.Set(Symbol{"eval"}, Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
+ repl_env.Set(Symbol{"*ARGV*"}, List{})
+
+ // 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))))))))")
+
+ // called with mal script to load and eval
+ if len(os.Args) > 1 {
+ args := make([]MalType, 0, len(os.Args)-2)
+ for _,a := range os.Args[2:] {
+ args = append(args, a)
+ }
+ repl_env.Set(Symbol{"*ARGV*"}, List{args,nil})
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/step9_try/step9_try.go b/go/src/step9_try/step9_try.go
new file mode 100644
index 0000000..18f3c9c
--- /dev/null
+++ b/go/src/step9_try/step9_try.go
@@ -0,0 +1,304 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+ "os"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func is_pair(x MalType) bool {
+ slc, e := GetSlice(x)
+ if e != nil { return false }
+ return len(slc) > 0
+}
+
+func quasiquote(ast MalType) MalType {
+ if !is_pair(ast) {
+ return List{[]MalType{Symbol{"quote"}, ast},nil}
+ } else {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && (a0.(Symbol).Val == "unquote") {
+ return slc[1]
+ } else if is_pair(a0) {
+ slc0, _ := GetSlice(a0)
+ a00 := slc0[0]
+ if Symbol_Q(a00) && (a00.(Symbol).Val == "splice-unquote") {
+ return List{[]MalType{Symbol{"concat"},
+ slc0[1],
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+ }
+ return List{[]MalType{Symbol{"cons"},
+ quasiquote(a0),
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+}
+
+func is_macro_call(ast MalType, env EnvType) bool {
+ if List_Q(ast) {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && env.Find(a0.(Symbol)) != nil {
+ mac, e := env.Get(a0.(Symbol))
+ if e != nil { return false }
+ if MalFunc_Q(mac) {
+ return mac.(MalFunc).GetMacro()
+ }
+ }
+ }
+ return false
+}
+
+func macroexpand(ast MalType, env EnvType) (MalType, error) {
+ var mac MalType
+ var e error
+ for ; is_macro_call(ast, env) ; {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ mac, e = env.Get(a0.(Symbol)); if e != nil { return nil, e }
+ fn := mac.(MalFunc)
+ ast, e = Apply(fn, slc[1:]); if e != nil { return nil, e }
+ }
+ return ast, nil
+}
+
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ var e error
+ for {
+
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ ast, e = macroexpand(ast, env); if e != nil { return nil, e }
+ if (!List_Q(ast)) { return ast, nil }
+
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ ast = a2
+ env = let_env
+ case "quote":
+ return a1, nil
+ case "quasiquote":
+ ast = quasiquote(a1)
+ case "defmacro!":
+ fn, e := EVAL(a2, env)
+ fn = fn.(MalFunc).SetMacro()
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), fn), nil
+ case "macroexpand":
+ return macroexpand(a1, env)
+ case "try*":
+ var exc MalType
+ exp, e := EVAL(a1, env)
+ if e == nil {
+ return exp, nil
+ } else {
+ if a2 != nil && List_Q(a2) {
+ a2s, _ := GetSlice(a2)
+ if Symbol_Q(a2s[0]) && (a2s[0].(Symbol).Val == "catch*") {
+ switch e.(type) {
+ case MalError: exc = e.(MalError).Obj
+ default: exc = e.Error()
+ }
+ binds := NewList(a2s[1])
+ new_env, e := NewEnv(env, binds, NewList(exc))
+ if e != nil { return nil, e }
+ exp, e = EVAL(a2s[2], new_env)
+ if e == nil { return exp, nil }
+ }
+ }
+ return nil, e
+ }
+ case "do":
+ lst := ast.(List).Val
+ _, e := eval_ast(List{lst[1:len(lst)-1],nil}, env)
+ if e != nil { return nil, e }
+ if len(lst) == 1 { return nil, nil }
+ ast = lst[len(lst)-1]
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ ast = ast.(List).Val[3]
+ } else {
+ return nil, nil
+ }
+ } else {
+ ast = a2
+ }
+ case "fn*":
+ fn := MalFunc{EVAL, a2, env, a1, false, NewEnv, nil}
+ return fn, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f := el.(List).Val[0]
+ if MalFunc_Q(f) {
+ fn := f.(MalFunc)
+ ast = fn.Exp
+ env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
+ if e != nil { return nil, e }
+ } else {
+ fn, ok := f.(Func)
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return fn.Fn(el.(List).Val[1:])
+ }
+ }
+
+ } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, Func{v.(func([]MalType)(MalType,error)),nil})
+ }
+ repl_env.Set(Symbol{"eval"}, Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
+ repl_env.Set(Symbol{"*ARGV*"}, List{})
+
+ // 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))))))))")
+
+ // called with mal script to load and eval
+ if len(os.Args) > 1 {
+ args := make([]MalType, 0, len(os.Args)-2)
+ for _,a := range os.Args[2:] {
+ args = append(args, a)
+ }
+ repl_env.Set(Symbol{"*ARGV*"}, List{args,nil})
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ // repl loop
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/stepA_mal/stepA_mal.go b/go/src/stepA_mal/stepA_mal.go
new file mode 100644
index 0000000..808022a
--- /dev/null
+++ b/go/src/stepA_mal/stepA_mal.go
@@ -0,0 +1,306 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "errors"
+ "os"
+)
+
+import (
+ "readline"
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func is_pair(x MalType) bool {
+ slc, e := GetSlice(x)
+ if e != nil { return false }
+ return len(slc) > 0
+}
+
+func quasiquote(ast MalType) MalType {
+ if !is_pair(ast) {
+ return List{[]MalType{Symbol{"quote"}, ast},nil}
+ } else {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && (a0.(Symbol).Val == "unquote") {
+ return slc[1]
+ } else if is_pair(a0) {
+ slc0, _ := GetSlice(a0)
+ a00 := slc0[0]
+ if Symbol_Q(a00) && (a00.(Symbol).Val == "splice-unquote") {
+ return List{[]MalType{Symbol{"concat"},
+ slc0[1],
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+ }
+ return List{[]MalType{Symbol{"cons"},
+ quasiquote(a0),
+ quasiquote(List{slc[1:],nil})},nil}
+ }
+}
+
+func is_macro_call(ast MalType, env EnvType) bool {
+ if List_Q(ast) {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ if Symbol_Q(a0) && env.Find(a0.(Symbol)) != nil {
+ mac, e := env.Get(a0.(Symbol))
+ if e != nil { return false }
+ if MalFunc_Q(mac) {
+ return mac.(MalFunc).GetMacro()
+ }
+ }
+ }
+ return false
+}
+
+func macroexpand(ast MalType, env EnvType) (MalType, error) {
+ var mac MalType
+ var e error
+ for ; is_macro_call(ast, env) ; {
+ slc, _ := GetSlice(ast)
+ a0 := slc[0]
+ mac, e = env.Get(a0.(Symbol)); if e != nil { return nil, e }
+ fn := mac.(MalFunc)
+ ast, e = Apply(fn, slc[1:]); if e != nil { return nil, e }
+ }
+ return ast, nil
+}
+
+func eval_ast(ast MalType, env EnvType) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol))
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst,nil}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst,nil}, nil
+ } else if HashMap_Q(ast) {
+ m := ast.(HashMap)
+ new_hm := HashMap{map[string]MalType{},nil}
+ for k, v := range m.Val {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm.Val[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env EnvType) (MalType, error) {
+ var e error
+ for {
+
+ //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ ast, e = macroexpand(ast, env); if e != nil { return nil, e }
+ if (!List_Q(ast)) { return ast, nil }
+
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), res), nil
+ case "let*":
+ let_env, e := NewEnv(env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol), exp)
+ }
+ ast = a2
+ env = let_env
+ case "quote":
+ return a1, nil
+ case "quasiquote":
+ ast = quasiquote(a1)
+ case "defmacro!":
+ fn, e := EVAL(a2, env)
+ fn = fn.(MalFunc).SetMacro()
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol), fn), nil
+ case "macroexpand":
+ return macroexpand(a1, env)
+ case "try*":
+ var exc MalType
+ exp, e := EVAL(a1, env)
+ if e == nil {
+ return exp, nil
+ } else {
+ if a2 != nil && List_Q(a2) {
+ a2s, _ := GetSlice(a2)
+ if Symbol_Q(a2s[0]) && (a2s[0].(Symbol).Val == "catch*") {
+ switch e.(type) {
+ case MalError: exc = e.(MalError).Obj
+ default: exc = e.Error()
+ }
+ binds := NewList(a2s[1])
+ new_env, e := NewEnv(env, binds, NewList(exc))
+ if e != nil { return nil, e }
+ exp, e = EVAL(a2s[2], new_env)
+ if e == nil { return exp, nil }
+ }
+ }
+ return nil, e
+ }
+ case "do":
+ lst := ast.(List).Val
+ _, e := eval_ast(List{lst[1:len(lst)-1],nil}, env)
+ if e != nil { return nil, e }
+ if len(lst) == 1 { return nil, nil }
+ ast = lst[len(lst)-1]
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ ast = ast.(List).Val[3]
+ } else {
+ return nil, nil
+ }
+ } else {
+ ast = a2
+ }
+ case "fn*":
+ fn := MalFunc{EVAL, a2, env, a1, false, NewEnv, nil}
+ return fn, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f := el.(List).Val[0]
+ if MalFunc_Q(f) {
+ fn := f.(MalFunc)
+ ast = fn.Exp
+ env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
+ if e != nil { return nil, e }
+ } else {
+ fn, ok := f.(Func)
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return fn.Fn(el.(List).Val[1:])
+ }
+ }
+
+ } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var res string
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if res, e = PRINT(exp); e != nil { return nil, e }
+ return res, nil
+}
+
+func main() {
+ // core.go: defined using go
+ for k, v := range core.NS {
+ repl_env.Set(Symbol{k}, Func{v.(func([]MalType)(MalType,error)),nil})
+ }
+ repl_env.Set(Symbol{"eval"}, Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
+ repl_env.Set(Symbol{"*ARGV*"}, List{})
+
+ // core.mal: defined using the language itself
+ rep("(def! *host-language* \"go\")")
+ 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))))))))")
+
+ // called with mal script to load and eval
+ if len(os.Args) > 1 {
+ args := make([]MalType, 0, len(os.Args)-2)
+ for _,a := range os.Args[2:] {
+ args = append(args, a)
+ }
+ repl_env.Set(Symbol{"*ARGV*"}, List{args,nil})
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ // repl loop
+ rep("(println (str \"Mal [\" *host-language* \"]\"))")
+ for {
+ text, err := readline.Readline("user> ")
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/types/types.go b/go/src/types/types.go
new file mode 100644
index 0000000..54e4176
--- /dev/null
+++ b/go/src/types/types.go
@@ -0,0 +1,258 @@
+package types
+
+import (
+ "reflect"
+ "errors"
+ "fmt"
+ "strings"
+)
+
+// Errors/Exceptions
+type MalError struct {
+ Obj MalType
+}
+
+func (e MalError) Error() string {
+ return fmt.Sprintf("%#v", e.Obj)
+}
+
+
+// General types
+type MalType interface {
+}
+
+type EnvType interface {
+ Find(key Symbol) EnvType
+ Set(key Symbol, value MalType) MalType
+ Get(key Symbol) (MalType, error)
+}
+
+// Scalars
+func Nil_Q(obj MalType) bool {
+ if obj == nil { return true } else { return false }
+}
+
+func True_Q(obj MalType) bool {
+ switch tobj := obj.(type) {
+ case bool: return tobj == true
+ default: return false
+ }
+}
+
+func False_Q(obj MalType) bool {
+ switch tobj := obj.(type) {
+ case bool: return tobj == false
+ default: return false
+ }
+}
+
+// Symbols
+type Symbol struct {
+ Val string
+}
+
+func Symbol_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "Symbol"
+}
+
+
+// Keywords
+func NewKeyword(s string) (MalType, error) {
+ return "\u029e" + s, nil;
+}
+
+func Keyword_Q(obj MalType) bool {
+ if obj == nil { return false }
+ switch s := obj.(type) {
+ case string: return strings.HasPrefix(s, "\u029e")
+ default: return false
+ }
+}
+
+
+// Strings
+func String_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "string"
+}
+
+
+// Functions
+type Func struct {
+ Fn func([]MalType) (MalType, error)
+ Meta MalType
+}
+
+func Func_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "Func"
+}
+
+type MalFunc struct {
+ Eval func(MalType, EnvType) (MalType, error)
+ Exp MalType
+ Env EnvType
+ Params MalType
+ IsMacro bool
+ GenEnv func(EnvType, MalType, MalType) (EnvType, error)
+ Meta MalType
+}
+
+func MalFunc_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "MalFunc"
+}
+
+func (f MalFunc) SetMacro() MalType {
+ f.IsMacro = true
+ return f
+}
+
+func (f MalFunc) GetMacro() bool {
+ return f.IsMacro
+}
+
+// Take either a MalFunc or regular function and apply it to the
+// arguments
+func Apply(f_mt MalType, a []MalType) (MalType, error) {
+ switch f := f_mt.(type) {
+ case MalFunc:
+ env, e := f.GenEnv(f.Env, f.Params, List{a,nil})
+ if e != nil { return nil, e }
+ return f.Eval(f.Exp, env)
+ case Func:
+ return f.Fn(a)
+ case func([]MalType)(MalType, error):
+ return f(a)
+ default:
+ return nil, errors.New("Invalid function to Apply")
+ }
+}
+
+
+// Lists
+type List struct {
+ Val []MalType
+ Meta MalType
+}
+
+func NewList(a ...MalType) MalType {
+ return List{a,nil}
+}
+
+func List_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "List"
+}
+
+// Vectors
+type Vector struct {
+ Val []MalType
+ Meta MalType
+}
+
+func Vector_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "Vector"
+}
+
+func GetSlice(seq MalType) ([]MalType, error) {
+ switch obj := seq.(type) {
+ case List: return obj.Val, nil
+ case Vector: return obj.Val, nil
+ default: return nil, errors.New("GetSlice called on non-sequence")
+ }
+}
+
+// Hash Maps
+type HashMap struct {
+ Val map[string]MalType
+ Meta MalType
+}
+
+func NewHashMap(seq MalType) (MalType, error) {
+ lst, e := GetSlice(seq)
+ if e != nil { return nil, e }
+ if len(lst) % 2 == 1 {
+ return nil, errors.New("Odd number of arguments to NewHashMap")
+ }
+ m := map[string]MalType{}
+ for i := 0; i < len(lst); i+=2 {
+ str, ok := lst[i].(string)
+ if !ok {
+ return nil, errors.New("expected hash-map key string")
+ }
+ m[str] = lst[i+1]
+ }
+ return HashMap{m,nil}, nil
+}
+
+func HashMap_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "HashMap"
+}
+
+// Atoms
+type Atom struct {
+ Val MalType
+ Meta MalType
+}
+func (a *Atom) Set(val MalType) MalType {
+ a.Val= val
+ return a
+}
+
+func Atom_Q(obj MalType) bool {
+ switch obj.(type) {
+ case *Atom: return true
+ default: return false
+ }
+}
+
+
+
+// General functions
+
+func _obj_type(obj MalType) string {
+ if obj == nil { return "nil" }
+ return reflect.TypeOf(obj).Name()
+}
+
+func Sequential_Q(seq MalType) bool {
+ if seq == nil { return false }
+ return (reflect.TypeOf(seq).Name() == "List") ||
+ (reflect.TypeOf(seq).Name() == "Vector")
+}
+
+func Equal_Q(a MalType, b MalType) bool {
+ ota := reflect.TypeOf(a); otb := reflect.TypeOf(b)
+ if !((ota == otb) || (Sequential_Q(a) && Sequential_Q(b))) {
+ return false
+ }
+ //av := reflect.ValueOf(a); bv := reflect.ValueOf(b)
+ //fmt.Printf("here2: %#v\n", reflect.TypeOf(a).Name())
+ //switch reflect.TypeOf(a).Name() {
+ switch a.(type) {
+ case Symbol:
+ return a.(Symbol).Val == b.(Symbol).Val
+ case List:
+ as,_ := GetSlice(a); bs,_ := GetSlice(b)
+ if len(as) != len(bs) { return false }
+ for i := 0; i < len(as); i+=1 {
+ if !Equal_Q(as[i], bs[i]) { return false }
+ }
+ return true
+ case Vector:
+ as,_ := GetSlice(a); bs,_ := GetSlice(b)
+ if len(as) != len(bs) { return false }
+ for i := 0; i < len(as); i+=1 {
+ if !Equal_Q(as[i], bs[i]) { return false }
+ }
+ return true
+ case HashMap:
+ return false
+ default:
+ return a == b
+ }
+}
diff --git a/haskell/Core.hs b/haskell/Core.hs
new file mode 100644
index 0000000..d1034c1
--- /dev/null
+++ b/haskell/Core.hs
@@ -0,0 +1,297 @@
+module Core
+( ns )
+where
+
+import System.IO (hFlush, stdout)
+import Control.Exception (catch)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import Data.Time.Clock.POSIX (getPOSIXTime)
+import Data.IORef (IORef, newIORef, readIORef, writeIORef)
+
+import Readline (readline)
+import Reader (read_str)
+import Types
+import Printer (_pr_str, _pr_list)
+
+-- General functions
+
+equal_Q [a, b] = return $ if a == b then MalTrue else MalFalse
+equal_Q _ = throwStr "illegal arguments to ="
+
+run_1 :: (MalVal -> MalVal) -> [MalVal] -> IOThrows MalVal
+run_1 f (x:[]) = return $ f x
+run_1 _ _ = throwStr "function takes a single argument"
+
+run_2 :: (MalVal -> MalVal -> MalVal) -> [MalVal] -> IOThrows MalVal
+run_2 f (x:y:[]) = return $ f x y
+run_2 _ _ = throwStr "function takes a two arguments"
+
+-- Error/Exception functions
+
+throw (mv:[]) = throwMalVal mv
+throw _ = throwStr "illegal arguments to throw"
+
+-- Scalar functions
+
+symbol (MalString str:[]) = return $ MalSymbol str
+symbol _ = throwStr "symbol called with non-string"
+
+keyword (MalString str:[]) = return $ MalString $ "\x029e" ++ str
+keyword _ = throwStr "keyword called with non-string"
+
+
+-- String functions
+
+pr_str args = do
+ return $ MalString $ _pr_list True " " args
+
+str args = do
+ return $ MalString $ _pr_list False "" args
+
+prn args = do
+ liftIO $ putStrLn $ _pr_list True " " args
+ liftIO $ hFlush stdout
+ return Nil
+
+println args = do
+ liftIO $ putStrLn $ _pr_list False " " args
+ liftIO $ hFlush stdout
+ return Nil
+
+slurp ([MalString path]) = do
+ str <- liftIO $ readFile path
+ return $ MalString str
+slurp _ = throwStr "invalid arguments to slurp"
+
+do_readline ([MalString prompt]) = do
+ str <- liftIO $ readline prompt
+ case str of
+ Nothing -> throwStr "readline failed"
+ Just str -> return $ MalString str
+do_readline _ = throwStr "invalid arguments to readline"
+
+-- Numeric functions
+
+num_op op [MalNumber a, MalNumber b] = do
+ return $ MalNumber $ op a b
+num_op _ _ = throwStr "illegal arguments to number operation"
+
+cmp_op op [MalNumber a, MalNumber b] = do
+ return $ if op a b then MalTrue else MalFalse
+cmp_op _ _ = throwStr "illegal arguments to comparison operation"
+
+time_ms _ = do
+ t <- liftIO $ getPOSIXTime
+ return $ MalNumber $ round (t * 1000)
+
+
+-- List functions
+
+list args = return $ MalList args Nil
+
+-- Vector functions
+
+vector args = return $ MalVector args Nil
+
+-- Hash Map functions
+
+_pairup [x] = throwStr "Odd number of elements to _pairup"
+_pairup [] = return []
+_pairup (MalString x:y:xs) = do
+ rest <- _pairup xs
+ return $ (x,y):rest
+
+hash_map args = do
+ pairs <- _pairup args
+ return $ MalHashMap (Map.fromList pairs) Nil
+
+assoc (MalHashMap hm _:kvs) = do
+ pairs <- _pairup kvs
+ return $ MalHashMap (Map.union (Map.fromList pairs) hm) Nil
+assoc _ = throwStr "invalid call to assoc"
+
+dissoc (MalHashMap hm _:ks) = do
+ let remover = (\hm (MalString k) -> Map.delete k hm) in
+ return $ MalHashMap (foldl remover hm ks) Nil
+dissoc _ = throwStr "invalid call to dissoc"
+
+get (MalHashMap hm _:MalString k:[]) = do
+ case Map.lookup k hm of
+ Just mv -> return mv
+ Nothing -> return Nil
+get (Nil:MalString k:[]) = return Nil
+get _ = throwStr "invalid call to get"
+
+contains_Q (MalHashMap hm _:MalString k:[]) = do
+ if Map.member k hm then return MalTrue
+ else return MalFalse
+contains_Q (Nil:MalString k:[]) = return MalFalse
+contains_Q _ = throwStr "invalid call to contains?"
+
+keys (MalHashMap hm _:[]) = do
+ return $ MalList (map MalString (Map.keys hm)) Nil
+keys _ = throwStr "invalid call to keys"
+
+vals (MalHashMap hm _:[]) = do
+ return $ MalList (Map.elems hm) Nil
+vals _ = throwStr "invalid call to vals"
+
+
+-- Sequence functions
+
+_sequential_Q (MalList _ _) = MalTrue
+_sequential_Q (MalVector _ _) = MalTrue
+_sequential_Q _ = MalFalse
+
+cons x Nil = MalList [x] Nil
+cons x (MalList lst _) = MalList (x:lst) Nil
+cons x (MalVector lst _) = MalList (x:lst) Nil
+
+concat1 a (MalList lst _) = a ++ lst
+concat1 a (MalVector lst _) = a ++ lst
+do_concat args = return $ MalList (foldl concat1 [] args) Nil
+
+nth ((MalList lst _):(MalNumber idx):[]) = do
+ if idx < length lst then return $ lst !! idx
+ else throwStr "nth: index out of range"
+nth ((MalVector lst _):(MalNumber idx):[]) = do
+ if idx < length lst then return $ lst !! idx
+ else throwStr "nth: index out of range"
+nth _ = throwStr "invalid call to nth"
+
+first (MalList lst _) = if length lst > 0 then lst !! 0 else Nil
+first (MalVector lst _) = if length lst > 0 then lst !! 0 else Nil
+
+rest (MalList lst _) = MalList (drop 1 lst) Nil
+rest (MalVector lst _) = MalList (drop 1 lst) Nil
+
+empty_Q Nil = MalTrue
+empty_Q (MalList [] _) = MalTrue
+empty_Q (MalVector [] _) = MalTrue
+empty_Q _ = MalFalse
+
+count (Nil:[]) = return $ MalNumber 0
+count (MalList lst _:[]) = return $ MalNumber $ length lst
+count (MalVector lst _:[]) = return $ MalNumber $ length lst
+count _ = throwStr $ "non-sequence passed to count"
+
+conj ((MalList lst _):args) = return $ MalList ((reverse args) ++ lst) Nil
+conj ((MalVector lst _):args) = return $ MalVector (lst ++ args) Nil
+conj _ = throwStr $ "illegal arguments to conj"
+
+apply args = do
+ f <- _get_call args
+ lst <- _to_list (last args)
+ f $ (init (drop 1 args)) ++ lst
+
+do_map args = do
+ f <- _get_call args
+ lst <- _to_list (args !! 1)
+ do new_lst <- mapM (\x -> f [x]) lst
+ return $ MalList new_lst Nil
+
+-- Metadata functions
+
+with_meta ((MalList lst _):m:[]) = return $ MalList lst m
+with_meta ((MalVector lst _):m:[]) = return $ MalVector lst m
+with_meta ((MalHashMap hm _):m:[]) = return $ MalHashMap hm m
+with_meta ((MalAtom atm _):m:[]) = return $ MalAtom atm m
+with_meta ((Func f _):m:[]) = return $ Func f m
+with_meta ((MalFunc {fn=f, ast=a, env=e, params=p, macro=mc}):m:[]) = do
+ return $ MalFunc {fn=f, ast=a, env=e, params=p, macro=mc, meta=m}
+with_meta _ = throwStr $ "invalid with-meta call"
+
+do_meta ((MalList _ m):[]) = return m
+do_meta ((MalVector _ m):[]) = return m
+do_meta ((MalHashMap _ m):[]) = return m
+do_meta ((MalAtom _ m):[]) = return m
+do_meta ((Func _ m):[]) = return m
+do_meta ((MalFunc {meta=m}):[]) = return m
+do_meta _ = throwStr $ "invalid meta call"
+
+-- Atom functions
+
+atom (val:[]) = do
+ ref <- liftIO $ newIORef val
+ return $ MalAtom ref Nil
+atom _ = throwStr "invalid atom call"
+
+deref (MalAtom ref _:[]) = do
+ val <- liftIO $ readIORef ref
+ return val
+deref _ = throwStr "invalid deref call"
+
+reset_BANG (MalAtom ref _:val:[]) = do
+ liftIO $ writeIORef ref $ val
+ return val
+reset_BANG _ = throwStr "invalid deref call"
+
+swap_BANG (MalAtom ref _:args) = do
+ val <- liftIO $ readIORef ref
+ f <- _get_call args
+ new_val <- f $ [val] ++ (tail args)
+ _ <- liftIO $ writeIORef ref $ new_val
+ return new_val
+
+ns = [
+ ("=", _func equal_Q),
+ ("throw", _func throw),
+ ("nil?", _func $ run_1 $ _nil_Q),
+ ("true?", _func $ run_1 $ _true_Q),
+ ("false?", _func $ run_1 $ _false_Q),
+ ("symbol", _func $ symbol),
+ ("symbol?", _func $ run_1 $ _symbol_Q),
+ ("keyword", _func $ keyword),
+ ("keyword?", _func $ run_1 $ _keyword_Q),
+
+ ("pr-str", _func pr_str),
+ ("str", _func str),
+ ("prn", _func prn),
+ ("println", _func println),
+ ("readline", _func do_readline),
+ ("read-string", _func (\[(MalString s)] -> read_str s)),
+ ("slurp", _func slurp),
+
+ ("<", _func $ cmp_op (<)),
+ ("<=", _func $ cmp_op (<=)),
+ (">", _func $ cmp_op (>)),
+ (">=", _func $ cmp_op (>=)),
+ ("+", _func $ num_op (+)),
+ ("-", _func $ num_op (-)),
+ ("*", _func $ num_op (*)),
+ ("/", _func $ num_op (div)),
+ ("time-ms", _func $ time_ms),
+
+ ("list", _func $ list),
+ ("list?", _func $ run_1 _list_Q),
+ ("vector", _func $ vector),
+ ("vector?", _func $ run_1 _vector_Q),
+ ("hash-map", _func $ hash_map),
+ ("map?", _func $ run_1 _hash_map_Q),
+ ("assoc", _func $ assoc),
+ ("dissoc", _func $ dissoc),
+ ("get", _func $ get),
+ ("contains?",_func $ contains_Q),
+ ("keys", _func $ keys),
+ ("vals", _func $ vals),
+
+ ("sequential?", _func $ run_1 _sequential_Q),
+ ("cons", _func $ run_2 $ cons),
+ ("concat", _func $ do_concat),
+ ("nth", _func nth),
+ ("first", _func $ run_1 $ first),
+ ("rest", _func $ run_1 $ rest),
+ ("empty?", _func $ run_1 $ empty_Q),
+ ("count", _func $ count),
+ ("conj", _func $ conj),
+ ("apply", _func $ apply),
+ ("map", _func $ do_map),
+
+ ("with-meta", _func $ with_meta),
+ ("meta", _func $ do_meta),
+ ("atom", _func $ atom),
+ ("atom?", _func $ run_1 _atom_Q),
+ ("deref", _func $ deref),
+ ("reset!", _func $ reset_BANG),
+ ("swap!", _func $ swap_BANG)]
diff --git a/haskell/Env.hs b/haskell/Env.hs
new file mode 100644
index 0000000..3dfd2c8
--- /dev/null
+++ b/haskell/Env.hs
@@ -0,0 +1,65 @@
+module Env
+( Env, env_new, null_env, env_bind, env_find, env_get, env_set )
+where
+
+import Data.IORef (IORef, newIORef, readIORef, writeIORef)
+import Control.Monad.Trans (liftIO)
+import Data.List (elemIndex)
+import qualified Data.Map as Map
+
+import Types
+import Printer
+
+-- These Env types are defined in Types module to avoid dep cycle
+--data EnvData = EnvPair (Maybe Env, (Map.Map String MalVal))
+--type Env = IORef EnvData
+
+env_new :: Maybe Env -> IO Env
+env_new outer = newIORef $ EnvPair (outer, (Map.fromList []))
+
+null_env = env_new Nothing
+
+env_bind :: Env -> [MalVal] -> [MalVal] -> IO Env
+env_bind envRef binds exprs = do
+ case (elemIndex (MalSymbol "&") binds) of
+ Nothing -> do
+ -- bind binds to exprs
+ _ <- mapM (\(b,e) -> env_set envRef b e) $ zip binds exprs
+ return envRef
+ Just idx -> do
+ -- Varargs binding
+ _ <- mapM (\(b,e) -> env_set envRef b e) $
+ zip (take idx binds) (take idx exprs)
+ _ <- env_set envRef (binds !! (idx + 1))
+ (MalList (drop idx exprs) Nil)
+ return envRef
+
+env_find :: Env -> MalVal -> IO (Maybe Env)
+env_find envRef sym@(MalSymbol key) = do
+ e <- readIORef envRef
+ case e of
+ EnvPair (o, m) -> case Map.lookup key m of
+ Nothing -> case o of
+ Nothing -> return Nothing
+ Just o -> env_find o sym
+ Just val -> return $ Just envRef
+
+env_get :: Env -> MalVal -> IOThrows MalVal
+env_get envRef sym@(MalSymbol key) = do
+ e1 <- liftIO $ env_find envRef sym
+ case e1 of
+ Nothing -> throwStr $ "'" ++ key ++ "' not found"
+ Just eRef -> do
+ e2 <- liftIO $ readIORef eRef
+ case e2 of
+ EnvPair (o,m) -> case Map.lookup key m of
+ Nothing -> throwStr $ "env_get error"
+ Just val -> return val
+
+
+env_set :: Env -> MalVal -> MalVal -> IO MalVal
+env_set envRef (MalSymbol key) val = do
+ e <- readIORef envRef
+ case e of
+ EnvPair (o,m) -> writeIORef envRef $ EnvPair (o, (Map.insert key val m))
+ return val
diff --git a/haskell/Makefile b/haskell/Makefile
new file mode 100644
index 0000000..0ac1a75
--- /dev/null
+++ b/haskell/Makefile
@@ -0,0 +1,31 @@
+SOURCES_BASE = Readline.hs Types.hs Reader.hs Printer.hs
+SOURCES_LISP = Env.hs Core.hs step9_try.hs
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#####################
+
+SRCS = step0_repl.hs step1_read_print.hs step2_eval.hs step3_env.hs \
+ step4_if_fn_do.hs step5_tco.hs step6_file.hs step7_quote.hs \
+ step8_macros.hs step9_try.hs stepA_mal.hs
+OTHER_SRCS = Readline.hs Types.hs Reader.hs Printer.hs Env.hs Core.hs
+BINS = $(SRCS:%.hs=%)
+
+#####################
+
+all: $(BINS) mal
+
+mal: $(word $(words $(BINS)),$(BINS))
+ cp $< $@
+
+$(BINS): %: %.hs $(OTHER_SRCS)
+ ghc --make $< -o $@
+
+clean:
+ rm -f $(BINS) mal *.hi *.o
+
+.PHONY: stats stats-lisp tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/haskell/Printer.hs b/haskell/Printer.hs
new file mode 100644
index 0000000..e24695f
--- /dev/null
+++ b/haskell/Printer.hs
@@ -0,0 +1,47 @@
+module Printer
+( _pr_str, _pr_list )
+where
+
+import qualified Data.Map as Map
+import Data.IORef (readIORef)
+import System.IO.Unsafe (unsafePerformIO)
+
+import Types
+
+--concat (map (++ delim) list)
+--join [] delim = []
+--join (x:xs) delim = x ++ delim ++ join xs delim
+
+
+_pr_list :: Bool -> String -> [MalVal] -> String
+_pr_list pr sep [] = []
+_pr_list pr sep (x:[]) = (_pr_str pr x)
+_pr_list pr sep (x:xs) = (_pr_str pr x) ++ sep ++ (_pr_list pr sep xs)
+
+_flatTuples ((a,b):xs) = MalString a : b : _flatTuples xs
+_flatTuples _ = []
+
+unescape chr = case chr of
+ '\n' -> "\\n"
+ '\\' -> "\\\\"
+ '"' -> "\\\""
+ c -> [c]
+
+_pr_str :: Bool -> MalVal -> String
+_pr_str _ (MalString ('\x029e':str)) = ":" ++ str
+_pr_str True (MalString str) = "\"" ++ concatMap unescape str ++ "\""
+_pr_str False (MalString str) = str
+_pr_str _ (MalSymbol name) = name
+_pr_str _ (MalNumber num) = show num
+_pr_str _ (MalTrue) = "true"
+_pr_str _ (MalFalse) = "false"
+_pr_str _ (Nil) = "nil"
+_pr_str pr (MalList items _) = "(" ++ (_pr_list pr " " items) ++ ")"
+_pr_str pr (MalVector items _) = "[" ++ (_pr_list pr " " items) ++ "]"
+_pr_str pr (MalHashMap m _) = "{" ++ (_pr_list pr " " (_flatTuples $ Map.assocs m)) ++ "}"
+_pr_str pr (MalAtom r _) = "(atom " ++ (_pr_str pr (unsafePerformIO (readIORef r))) ++ ")"
+_pr_str _ (Func f _) = "#<function>"
+_pr_str _ (MalFunc {ast=ast, env=fn_env, params=params}) = "(fn* " ++ (show params) ++ " " ++ (show ast) ++ ")"
+
+instance Show MalVal where show = _pr_str True
+
diff --git a/haskell/Reader.hs b/haskell/Reader.hs
new file mode 100644
index 0000000..91ce63d
--- /dev/null
+++ b/haskell/Reader.hs
@@ -0,0 +1,155 @@
+module Reader
+( read_str )
+where
+
+import Text.ParserCombinators.Parsec (
+ Parser, parse, space, char, digit, letter, try,
+ (<|>), oneOf, noneOf, many, many1, skipMany, skipMany1, sepEndBy)
+import qualified Data.Map as Map
+import Control.Monad (liftM)
+
+import Types
+
+spaces :: Parser ()
+spaces = skipMany1 (oneOf ", \n")
+
+comment :: Parser ()
+comment = do
+ char ';'
+ skipMany (noneOf "\r\n")
+
+ignored :: Parser ()
+ignored = skipMany (spaces <|> comment)
+
+symbol :: Parser Char
+symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
+
+escaped :: Parser Char
+escaped = do
+ char '\\'
+ x <- oneOf "\\\"n"
+ case x of
+ 'n' -> return '\n'
+ _ -> return x
+
+read_number :: Parser MalVal
+read_number = liftM (MalNumber . read) $ many1 digit
+
+read_string :: Parser MalVal
+read_string = do
+ char '"'
+ x <- many (escaped <|> noneOf "\\\"")
+ char '"'
+ return $ MalString x
+
+read_symbol :: Parser MalVal
+read_symbol = do
+ first <- letter <|> symbol
+ rest <- many (letter <|> digit <|> symbol)
+ let str = first:rest
+ return $ case str of
+ "true" -> MalTrue
+ "false" -> MalFalse
+ "nil" -> Nil
+ _ -> MalSymbol str
+
+read_keyword :: Parser MalVal
+read_keyword = do
+ char ':'
+ x <- many (letter <|> digit <|> symbol)
+ return $ MalString $ "\x029e" ++ x
+
+read_atom :: Parser MalVal
+read_atom = read_number
+ <|> read_string
+ <|> read_keyword
+ <|> read_symbol
+
+read_list :: Parser MalVal
+read_list = do
+ char '('
+ x <- sepEndBy read_form ignored
+ char ')'
+ return $ MalList x Nil
+
+read_vector :: Parser MalVal
+read_vector = do
+ char '['
+ x <- sepEndBy read_form ignored
+ char ']'
+ return $ MalVector x Nil
+
+-- TODO: propagate error properly
+_pairs [x] = error "Odd number of elements to _pairs"
+_pairs [] = []
+_pairs (MalString x:y:xs) = (x,y):_pairs xs
+
+read_hash_map :: Parser MalVal
+read_hash_map = do
+ char '{'
+ x <- sepEndBy read_form ignored
+ char '}'
+ return $ MalHashMap (Map.fromList $ _pairs x) Nil
+
+-- reader macros
+read_quote :: Parser MalVal
+read_quote = do
+ char '\''
+ x <- read_form
+ return $ MalList [MalSymbol "quote", x] Nil
+
+read_quasiquote :: Parser MalVal
+read_quasiquote = do
+ char '`'
+ x <- read_form
+ return $ MalList [MalSymbol "quasiquote", x] Nil
+
+read_splice_unquote :: Parser MalVal
+read_splice_unquote = do
+ char '~'
+ char '@'
+ x <- read_form
+ return $ MalList [MalSymbol "splice-unquote", x] Nil
+
+read_unquote :: Parser MalVal
+read_unquote = do
+ char '~'
+ x <- read_form
+ return $ MalList [MalSymbol "unquote", x] Nil
+
+read_deref :: Parser MalVal
+read_deref = do
+ char '@'
+ x <- read_form
+ return $ MalList [MalSymbol "deref", x] Nil
+
+read_with_meta :: Parser MalVal
+read_with_meta = do
+ char '^'
+ m <- read_form
+ x <- read_form
+ return $ MalList [MalSymbol "with-meta", x, m] Nil
+
+read_macro :: Parser MalVal
+read_macro = read_quote
+ <|> read_quasiquote
+ <|> try read_splice_unquote <|> read_unquote
+ <|> read_deref
+ <|> read_with_meta
+
+--
+
+read_form :: Parser MalVal
+read_form = do
+ ignored
+ x <- read_macro
+ <|> read_list
+ <|> read_vector
+ <|> read_hash_map
+ <|> read_atom
+ return $ x
+
+read_str :: String -> IOThrows MalVal
+read_str str = case parse read_form "Mal" str of
+ Left err -> throwStr $ show err
+ Right val -> return val
diff --git a/haskell/Readline.hs b/haskell/Readline.hs
new file mode 100644
index 0000000..bbde009
--- /dev/null
+++ b/haskell/Readline.hs
@@ -0,0 +1,32 @@
+module Readline
+( readline, load_history )
+where
+
+-- Pick one of these:
+-- GPL license
+import qualified System.Console.Readline as RL
+-- BSD license
+--import qualified System.Console.Editline.Readline as RL
+
+import System.Directory (getHomeDirectory)
+
+import System.IO (hGetLine, hFlush, hIsEOF, stdin, stdout)
+
+history_file = do
+ home <- getHomeDirectory
+ return $ home ++ "/.mal-history"
+
+load_history = do
+ hfile <- history_file
+ content <- readFile hfile
+ mapM RL.addHistory (lines content)
+
+readline prompt = do
+ hfile <- history_file
+ maybeLine <- RL.readline prompt
+ case maybeLine of
+ Just line -> do
+ appendFile hfile (line ++ "\n")
+ RL.addHistory line
+ return maybeLine
+ _ -> return maybeLine
diff --git a/haskell/Types.hs b/haskell/Types.hs
new file mode 100644
index 0000000..5a7fff7
--- /dev/null
+++ b/haskell/Types.hs
@@ -0,0 +1,136 @@
+module Types
+(MalVal (..), MalError (..), IOThrows (..), Fn (..), EnvData (..), Env,
+ throwStr, throwMalVal, _get_call, _to_list,
+ _func, _malfunc,
+ _nil_Q, _true_Q, _false_Q, _symbol_Q, _keyword_Q,
+ _list_Q, _vector_Q, _hash_map_Q, _atom_Q)
+where
+
+import Data.IORef (IORef)
+import qualified Data.Map as Map
+import Control.Exception as CE
+import Control.Monad.Error (ErrorT, Error, noMsg, strMsg, throwError)
+
+
+-- Base Mal types --
+newtype Fn = Fn ([MalVal] -> IOThrows MalVal)
+data MalVal = Nil
+ | MalFalse
+ | MalTrue
+ | MalNumber Int
+ | MalString String
+ | MalSymbol String
+ | MalList [MalVal] MalVal
+ | MalVector [MalVal] MalVal
+ | MalHashMap (Map.Map String MalVal) MalVal
+ | MalAtom (IORef MalVal) MalVal
+ | Func Fn MalVal
+ | MalFunc {fn :: Fn,
+ ast :: MalVal,
+ env :: Env,
+ params :: MalVal,
+ macro :: Bool,
+ meta :: MalVal}
+
+_equal_Q Nil Nil = True
+_equal_Q MalFalse MalFalse = True
+_equal_Q MalTrue MalTrue = True
+_equal_Q (MalNumber a) (MalNumber b) = a == b
+_equal_Q (MalString a) (MalString b) = a == b
+_equal_Q (MalSymbol a) (MalSymbol b) = a == b
+_equal_Q (MalList a _) (MalList b _) = a == b
+_equal_Q (MalList a _) (MalVector b _) = a == b
+_equal_Q (MalVector a _) (MalList b _) = a == b
+_equal_Q (MalHashMap a _) (MalHashMap b _) = a == b
+_equal_Q (MalAtom a _) (MalAtom b _) = a == b
+_equal_Q _ _ = False
+
+instance Eq MalVal where
+ x == y = _equal_Q x y
+
+
+--- Errors/Exceptions ---
+
+data MalError = StringError String
+ | MalValError MalVal
+
+type IOThrows = ErrorT MalError IO
+
+instance Error MalError where
+ noMsg = StringError "An error has occurred"
+ strMsg = StringError
+
+throwStr str = throwError $ StringError str
+throwMalVal mv = throwError $ MalValError mv
+
+-- Env types --
+-- Note: Env functions are in Env module
+data EnvData = EnvPair (Maybe Env, (Map.Map String MalVal))
+type Env = IORef EnvData
+
+
+
+----------------------------------------------------------
+
+-- General functions --
+
+_get_call ((Func (Fn f) _) : _) = return f
+_get_call (MalFunc {fn=(Fn f)} : _) = return f
+_get_call _ = throwStr "_get_call first parameter is not a function "
+
+_to_list (MalList lst _) = return lst
+_to_list (MalVector lst _) = return lst
+_to_list _ = throwStr "_to_list expected a MalList or MalVector"
+
+-- Errors
+
+--catchAny :: IO a -> (CE.SomeException -> IO a) -> IO a
+--catchAny = CE.catch
+
+-- Functions
+
+_func fn = Func (Fn fn) Nil
+_func_meta fn meta = Func (Fn fn) meta
+
+_malfunc ast env params fn = MalFunc {fn=(Fn fn), ast=ast,
+ env=env, params=params,
+ macro=False, meta=Nil}
+_malfunc_meta ast env params fn meta = MalFunc {fn=(Fn fn), ast=ast,
+ env=env, params=params,
+ macro=False, meta=meta}
+
+-- Scalars
+_nil_Q Nil = MalTrue
+_nil_Q _ = MalFalse
+
+_true_Q MalTrue = MalTrue
+_true_Q _ = MalFalse
+
+_false_Q MalFalse = MalTrue
+_false_Q _ = MalFalse
+
+_symbol_Q (MalSymbol _) = MalTrue
+_symbol_Q _ = MalFalse
+
+_keyword_Q (MalString ('\x029e':_)) = MalTrue
+_keyword_Q _ = MalFalse
+
+-- Lists
+
+_list_Q (MalList _ _) = MalTrue
+_list_Q _ = MalFalse
+
+-- Vectors
+
+_vector_Q (MalVector _ _) = MalTrue
+_vector_Q _ = MalFalse
+
+-- Hash Maps
+
+_hash_map_Q (MalHashMap _ _) = MalTrue
+_hash_map_Q _ = MalFalse
+
+-- Atoms
+
+_atom_Q (MalAtom _ _) = MalTrue
+_atom_Q _ = MalFalse
diff --git a/haskell/step0_repl.hs b/haskell/step0_repl.hs
new file mode 100644
index 0000000..6396400
--- /dev/null
+++ b/haskell/step0_repl.hs
@@ -0,0 +1,28 @@
+import System.IO (hFlush, stdout)
+
+import Readline (readline, load_history)
+
+-- read
+mal_read str = str
+
+-- eval
+eval ast env = ast
+
+-- print
+mal_print exp = exp
+
+-- repl
+rep line = mal_print $ eval (mal_read line) ""
+
+repl_loop = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop
+ Just str -> do
+ putStrLn $ rep str
+ repl_loop
+
+main = do
+ load_history
+ repl_loop
diff --git a/haskell/step1_read_print.hs b/haskell/step1_read_print.hs
new file mode 100644
index 0000000..c7a4eef
--- /dev/null
+++ b/haskell/step1_read_print.hs
@@ -0,0 +1,45 @@
+import System.IO (hFlush, stdout)
+import Control.Monad.Error (runErrorT)
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+eval :: MalVal -> String -> MalVal
+eval ast env = ast
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+rep :: String -> IOThrows String
+rep line = do
+ ast <- mal_read line
+ return $ mal_print (eval ast "")
+
+repl_loop :: IO ()
+repl_loop = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop
+ Just str -> do
+ res <- runErrorT $ rep str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop
+
+main = do
+ load_history
+ repl_loop
diff --git a/haskell/step2_eval.hs b/haskell/step2_eval.hs
new file mode 100644
index 0000000..9105737
--- /dev/null
+++ b/haskell/step2_eval.hs
@@ -0,0 +1,93 @@
+import System.IO (hFlush, stdout)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+eval_ast :: MalVal -> (Map.Map String MalVal) -> IOThrows MalVal
+eval_ast (MalSymbol sym) env = do
+ case Map.lookup sym env of
+ Nothing -> throwStr $ "'" ++ sym ++ "' not found"
+ Just v -> return v
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+apply_ast :: MalVal -> (Map.Map String MalVal) -> IOThrows MalVal
+apply_ast ast@(MalList _ _) env = do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+
+eval :: MalVal -> (Map.Map String MalVal) -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+add [MalNumber a, MalNumber b] = return $ MalNumber $ a + b
+add _ = throwStr $ "illegal arguments to +"
+sub [MalNumber a, MalNumber b] = return $ MalNumber $ a - b
+sub _ = throwStr $ "illegal arguments to -"
+mult [MalNumber a, MalNumber b] = return $ MalNumber $ a * b
+mult _ = throwStr $ "illegal arguments to *"
+divd [MalNumber a, MalNumber b] = return $ MalNumber $ a `div` b
+divd _ = throwStr $ "illegal arguments to /"
+
+repl_env :: Map.Map String MalVal
+repl_env = Map.fromList [("+", _func add),
+ ("-", _func sub),
+ ("*", _func mult),
+ ("/", _func divd)]
+
+rep :: String -> IOThrows String
+rep line = do
+ ast <- mal_read line
+ exp <- eval ast repl_env
+ return $ mal_print exp
+
+repl_loop :: IO ()
+repl_loop = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop
+ Just str -> do
+ res <- runErrorT $ rep str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop
+
+main = do
+ load_history
+ repl_loop
diff --git a/haskell/step3_env.hs b/haskell/step3_env.hs
new file mode 100644
index 0000000..3bd3b19
--- /dev/null
+++ b/haskell/step3_env.hs
@@ -0,0 +1,113 @@
+import System.IO (hFlush, stdout)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_get, env_set)
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList _ _) env = do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+add [MalNumber a, MalNumber b] = return $ MalNumber $ a + b
+add _ = throwStr $ "illegal arguments to +"
+sub [MalNumber a, MalNumber b] = return $ MalNumber $ a - b
+sub _ = throwStr $ "illegal arguments to -"
+mult [MalNumber a, MalNumber b] = return $ MalNumber $ a * b
+mult _ = throwStr $ "illegal arguments to *"
+divd [MalNumber a, MalNumber b] = return $ MalNumber $ a `div` b
+divd _ = throwStr $ "illegal arguments to /"
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ load_history
+
+ repl_env <- env_new Nothing
+ env_set repl_env (MalSymbol "+") $ _func add
+ env_set repl_env (MalSymbol "-") $ _func sub
+ env_set repl_env (MalSymbol "*") $ _func mult
+ env_set repl_env (MalSymbol "/") $ _func divd
+ repl_loop repl_env
diff --git a/haskell/step4_if_fn_do.hs b/haskell/step4_if_fn_do.hs
new file mode 100644
index 0000000..497ece2
--- /dev/null
+++ b/haskell/step4_if_fn_do.hs
@@ -0,0 +1,140 @@
+import System.IO (hFlush, stdout)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_func
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+
+ repl_loop repl_env
diff --git a/haskell/step5_tco.hs b/haskell/step5_tco.hs
new file mode 100644
index 0000000..f32875a
--- /dev/null
+++ b/haskell/step5_tco.hs
@@ -0,0 +1,144 @@
+import System.IO (hFlush, stdout)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_malfunc a2 env (MalList params Nil)
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ (MalList ((MalFunc {ast=ast, env=fn_env, params=(MalList params Nil)}) : rest) _) -> do
+ fn_env1 <- liftIO $ env_new $ Just fn_env
+ fn_env2 <- liftIO $ env_bind fn_env1 params rest
+ eval ast fn_env2
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+
+ repl_loop repl_env
diff --git a/haskell/step6_file.hs b/haskell/step6_file.hs
new file mode 100644
index 0000000..ba58f2f
--- /dev/null
+++ b/haskell/step6_file.hs
@@ -0,0 +1,154 @@
+import System.IO (hFlush, stdout)
+import System.Environment (getArgs)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_malfunc a2 env (MalList params Nil)
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ (MalList ((MalFunc {ast=ast, env=fn_env, params=(MalList params Nil)}) : rest) _) -> do
+ fn_env1 <- liftIO $ env_new $ Just fn_env
+ fn_env2 <- liftIO $ env_bind fn_env1 params rest
+ eval ast fn_env2
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ args <- getArgs
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+ env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env))
+ env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+ runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+
+ if length args > 0 then do
+ env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
+ runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
+ return ()
+ else
+ repl_loop repl_env
diff --git a/haskell/step7_quote.hs b/haskell/step7_quote.hs
new file mode 100644
index 0000000..c6bb0e0
--- /dev/null
+++ b/haskell/step7_quote.hs
@@ -0,0 +1,183 @@
+import System.IO (hFlush, stdout)
+import System.Environment (getArgs)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+is_pair (MalList x _:xs) = True
+is_pair (MalVector x _:xs) = True
+is_pair _ = False
+
+quasiquote :: MalVal -> MalVal
+quasiquote ast =
+ case ast of
+ (MalList (MalSymbol "unquote" : a1 : []) _) -> a1
+ (MalList (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalList rest Nil)] Nil
+ (MalVector (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalVector rest Nil)] Nil
+ (MalList (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalList rest Nil)] Nil
+ (MalVector (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalVector rest Nil)] Nil
+ _ -> MalList [(MalSymbol "quote"), ast] Nil
+
+
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "quote" : args) _) env = do
+ case args of
+ a1 : [] -> return a1
+ _ -> throwStr "invalid quote"
+apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do
+ case args of
+ a1 : [] -> eval (quasiquote a1) env
+ _ -> throwStr "invalid quasiquote"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_malfunc a2 env (MalList params Nil)
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ (MalList ((MalFunc {ast=ast, env=fn_env, params=(MalList params Nil)}) : rest) _) -> do
+ fn_env1 <- liftIO $ env_new $ Just fn_env
+ fn_env2 <- liftIO $ env_bind fn_env1 params rest
+ eval ast fn_env2
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ args <- getArgs
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+ env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env))
+ env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+ runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+
+ if length args > 0 then do
+ env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
+ runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
+ return ()
+ else
+ repl_loop repl_env
diff --git a/haskell/step8_macros.hs b/haskell/step8_macros.hs
new file mode 100644
index 0000000..9b272e8
--- /dev/null
+++ b/haskell/step8_macros.hs
@@ -0,0 +1,238 @@
+import System.IO (hFlush, stdout)
+import System.Environment (getArgs)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_find, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+is_pair (MalList x _:xs) = True
+is_pair (MalVector x _:xs) = True
+is_pair _ = False
+
+quasiquote :: MalVal -> MalVal
+quasiquote ast =
+ case ast of
+ (MalList (MalSymbol "unquote" : a1 : []) _) -> a1
+ (MalList (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalList rest Nil)] Nil
+ (MalVector (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalVector rest Nil)] Nil
+ (MalList (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalList rest Nil)] Nil
+ (MalVector (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalVector rest Nil)] Nil
+ _ -> MalList [(MalSymbol "quote"), ast] Nil
+
+is_macro_call :: MalVal -> Env -> IOThrows Bool
+is_macro_call (MalList (a0@(MalSymbol _) : rest) _) env = do
+ e <- liftIO $ env_find env a0
+ case e of
+ Just e -> do
+ f <- env_get e a0
+ case f of
+ MalFunc {macro=True} -> return True
+ _ -> return False
+ Nothing -> return False
+is_macro_call _ _ = return False
+
+macroexpand :: MalVal -> Env -> IOThrows MalVal
+macroexpand ast@(MalList (a0 : args) _) env = do
+ mc <- is_macro_call ast env
+ if mc then do
+ mac <- env_get env a0
+ case mac of
+ MalFunc {fn=(Fn f)} -> do
+ new_ast <- f args
+ macroexpand new_ast env
+ _ ->
+ return ast
+ else
+ return ast
+macroexpand ast _ = return ast
+
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "quote" : args) _) env = do
+ case args of
+ a1 : [] -> return a1
+ _ -> throwStr "invalid quote"
+apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do
+ case args of
+ a1 : [] -> eval (quasiquote a1) env
+ _ -> throwStr "invalid quasiquote"
+
+apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ func <- eval a2 env
+ case func of
+ MalFunc {fn=f, ast=a, env=e, params=p} -> do
+ let new_func = MalFunc {fn=f, ast=a, env=e,
+ params=p, macro=True,
+ meta=Nil} in
+ liftIO $ env_set env a1 new_func
+ _ -> throwStr "defmacro! on non-function"
+ _ -> throwStr "invalid defmacro!"
+apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do
+ case args of
+ (a1 : []) -> macroexpand a1 env
+ _ -> throwStr "invalid macroexpand"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_malfunc a2 env (MalList params Nil)
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ mc <- is_macro_call ast env
+ if mc then do
+ new_ast <- macroexpand ast env
+ eval new_ast env
+ else
+ case ast of
+ MalList _ _ -> do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ (MalList ((MalFunc {ast=ast,
+ env=fn_env,
+ params=(MalList params Nil)} : rest)) _) -> do
+ fn_env1 <- liftIO $ env_new $ Just fn_env
+ fn_env2 <- liftIO $ env_bind fn_env1 params rest
+ eval ast fn_env2
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+ _ -> return ast
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ args <- getArgs
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+ env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env))
+ env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+ runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+ runErrorT $ rep repl_env "(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)))))))"
+ runErrorT $ rep repl_env "(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 then do
+ env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
+ runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
+ return ()
+ else
+ repl_loop repl_env
diff --git a/haskell/step9_try.hs b/haskell/step9_try.hs
new file mode 100644
index 0000000..f944d17
--- /dev/null
+++ b/haskell/step9_try.hs
@@ -0,0 +1,253 @@
+import System.IO (hFlush, stdout)
+import System.Environment (getArgs)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_find, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+is_pair (MalList x _:xs) = True
+is_pair (MalVector x _:xs) = True
+is_pair _ = False
+
+quasiquote :: MalVal -> MalVal
+quasiquote ast =
+ case ast of
+ (MalList (MalSymbol "unquote" : a1 : []) _) -> a1
+ (MalList (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalList rest Nil)] Nil
+ (MalVector (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalVector rest Nil)] Nil
+ (MalList (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalList rest Nil)] Nil
+ (MalVector (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalVector rest Nil)] Nil
+ _ -> MalList [(MalSymbol "quote"), ast] Nil
+
+is_macro_call :: MalVal -> Env -> IOThrows Bool
+is_macro_call (MalList (a0@(MalSymbol _) : rest) _) env = do
+ e <- liftIO $ env_find env a0
+ case e of
+ Just e -> do
+ f <- env_get e a0
+ case f of
+ MalFunc {macro=True} -> return True
+ _ -> return False
+ Nothing -> return False
+is_macro_call _ _ = return False
+
+macroexpand :: MalVal -> Env -> IOThrows MalVal
+macroexpand ast@(MalList (a0 : args) _) env = do
+ mc <- is_macro_call ast env
+ if mc then do
+ mac <- env_get env a0
+ case mac of
+ MalFunc {fn=(Fn f)} -> do
+ new_ast <- f args
+ macroexpand new_ast env
+ _ ->
+ return ast
+ else
+ return ast
+macroexpand ast _ = return ast
+
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "quote" : args) _) env = do
+ case args of
+ a1 : [] -> return a1
+ _ -> throwStr "invalid quote"
+apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do
+ case args of
+ a1 : [] -> eval (quasiquote a1) env
+ _ -> throwStr "invalid quasiquote"
+
+apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ func <- eval a2 env
+ case func of
+ MalFunc {fn=f, ast=a, env=e, params=p} -> do
+ let new_func = MalFunc {fn=f, ast=a, env=e,
+ params=p, macro=True,
+ meta=Nil} in
+ liftIO $ env_set env a1 new_func
+ _ -> throwStr "defmacro! on non-function"
+ _ -> throwStr "invalid defmacro!"
+apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do
+ case args of
+ (a1 : []) -> macroexpand a1 env
+ _ -> throwStr "invalid macroexpand"
+apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do
+ case args of
+ (a1 : []) -> eval a1 env
+ (a1 : (MalList ((MalSymbol "catch*") : a21 : a22 : []) _) : []) -> do
+ res <- liftIO $ runErrorT $ eval a1 env
+ case res of
+ Right val -> return val
+ Left err -> do
+ exc <- case err of
+ (StringError str) -> return $ MalString str
+ (MalValError mv) -> return $ mv
+ try_env <- liftIO $ env_new $ Just env
+ liftIO $ env_set try_env a21 exc
+ eval a22 try_env
+ _ -> throwStr "invalid try*"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_malfunc a2 env (MalList params Nil)
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ mc <- is_macro_call ast env
+ if mc then do
+ new_ast <- macroexpand ast env
+ eval new_ast env
+ else
+ case ast of
+ MalList _ _ -> do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ (MalList ((MalFunc {ast=ast,
+ env=fn_env,
+ params=(MalList params Nil)} : rest)) _) -> do
+ fn_env1 <- liftIO $ env_new $ Just fn_env
+ fn_env2 <- liftIO $ env_bind fn_env1 params rest
+ eval ast fn_env2
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+ _ -> return ast
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ args <- getArgs
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+ env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env))
+ env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+ runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+ runErrorT $ rep repl_env "(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)))))))"
+ runErrorT $ rep repl_env "(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 then do
+ env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
+ runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
+ return ()
+ else
+ repl_loop repl_env
diff --git a/haskell/stepA_mal.hs b/haskell/stepA_mal.hs
new file mode 100644
index 0000000..f1d4b38
--- /dev/null
+++ b/haskell/stepA_mal.hs
@@ -0,0 +1,255 @@
+import System.IO (hFlush, stdout)
+import System.Environment (getArgs)
+import Control.Monad (mapM)
+import Control.Monad.Error (runErrorT)
+import Control.Monad.Trans (liftIO)
+import qualified Data.Map as Map
+import qualified Data.Traversable as DT
+
+import Readline (readline, load_history)
+import Types
+import Reader (read_str)
+import Printer (_pr_str)
+import Env (Env, env_new, env_bind, env_find, env_get, env_set)
+import Core as Core
+
+-- read
+mal_read :: String -> IOThrows MalVal
+mal_read str = read_str str
+
+-- eval
+is_pair (MalList x _:xs) = True
+is_pair (MalVector x _:xs) = True
+is_pair _ = False
+
+quasiquote :: MalVal -> MalVal
+quasiquote ast =
+ case ast of
+ (MalList (MalSymbol "unquote" : a1 : []) _) -> a1
+ (MalList (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalList rest Nil)] Nil
+ (MalVector (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
+ MalList [(MalSymbol "concat"), a01, quasiquote (MalVector rest Nil)] Nil
+ (MalList (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalList rest Nil)] Nil
+ (MalVector (a0 : rest) _) -> MalList [(MalSymbol "cons"),
+ quasiquote a0,
+ quasiquote (MalVector rest Nil)] Nil
+ _ -> MalList [(MalSymbol "quote"), ast] Nil
+
+is_macro_call :: MalVal -> Env -> IOThrows Bool
+is_macro_call (MalList (a0@(MalSymbol _) : rest) _) env = do
+ e <- liftIO $ env_find env a0
+ case e of
+ Just e -> do
+ f <- env_get e a0
+ case f of
+ MalFunc {macro=True} -> return True
+ _ -> return False
+ Nothing -> return False
+is_macro_call _ _ = return False
+
+macroexpand :: MalVal -> Env -> IOThrows MalVal
+macroexpand ast@(MalList (a0 : args) _) env = do
+ mc <- is_macro_call ast env
+ if mc then do
+ mac <- env_get env a0
+ case mac of
+ MalFunc {fn=(Fn f)} -> do
+ new_ast <- f args
+ macroexpand new_ast env
+ _ ->
+ return ast
+ else
+ return ast
+macroexpand ast _ = return ast
+
+eval_ast :: MalVal -> Env -> IOThrows MalVal
+eval_ast sym@(MalSymbol _) env = env_get env sym
+eval_ast ast@(MalList lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalList new_lst m
+eval_ast ast@(MalVector lst m) env = do
+ new_lst <- mapM (\x -> (eval x env)) lst
+ return $ MalVector new_lst m
+eval_ast ast@(MalHashMap lst m) env = do
+ new_hm <- DT.mapM (\x -> (eval x env)) lst
+ return $ MalHashMap new_hm m
+eval_ast ast env = return ast
+
+let_bind :: Env -> [MalVal] -> IOThrows Env
+let_bind env [] = return env
+let_bind env (b:e:xs) = do
+ evaled <- eval e env
+ x <- liftIO $ env_set env b evaled
+ let_bind env xs
+
+apply_ast :: MalVal -> Env -> IOThrows MalVal
+apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
+ case args of
+ (a1@(MalSymbol _): a2 : []) -> do
+ evaled <- eval a2 env
+ liftIO $ env_set env a1 evaled
+ _ -> throwStr "invalid def!"
+apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ let_env <- liftIO $ env_new $ Just env
+ let_bind let_env params
+ eval a2 let_env
+ _ -> throwStr "invalid let*"
+apply_ast ast@(MalList (MalSymbol "quote" : args) _) env = do
+ case args of
+ a1 : [] -> return a1
+ _ -> throwStr "invalid quote"
+apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do
+ case args of
+ a1 : [] -> eval (quasiquote a1) env
+ _ -> throwStr "invalid quasiquote"
+
+apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ func <- eval a2 env
+ case func of
+ MalFunc {fn=f, ast=a, env=e, params=p} -> do
+ let new_func = MalFunc {fn=f, ast=a, env=e,
+ params=p, macro=True,
+ meta=Nil} in
+ liftIO $ env_set env a1 new_func
+ _ -> throwStr "defmacro! on non-function"
+ _ -> throwStr "invalid defmacro!"
+apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do
+ case args of
+ (a1 : []) -> macroexpand a1 env
+ _ -> throwStr "invalid macroexpand"
+apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do
+ case args of
+ (a1 : []) -> eval a1 env
+ (a1 : (MalList ((MalSymbol "catch*") : a21 : a22 : []) _) : []) -> do
+ res <- liftIO $ runErrorT $ eval a1 env
+ case res of
+ Right val -> return val
+ Left err -> do
+ exc <- case err of
+ (StringError str) -> return $ MalString str
+ (MalValError mv) -> return $ mv
+ try_env <- liftIO $ env_new $ Just env
+ liftIO $ env_set try_env a21 exc
+ eval a22 try_env
+ _ -> throwStr "invalid try*"
+apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
+ case args of
+ ([]) -> return Nil
+ _ -> do
+ el <- eval_ast (MalList args Nil) env
+ case el of
+ (MalList lst _) -> return $ last lst
+
+apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
+ case args of
+ (a1 : a2 : a3 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then eval a3 env
+ else eval a2 env
+ (a1 : a2 : []) -> do
+ cond <- eval a1 env
+ if cond == MalFalse || cond == Nil
+ then return Nil
+ else eval a2 env
+ _ -> throwStr "invalid if"
+apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
+ case args of
+ (a1 : a2 : []) -> do
+ params <- (_to_list a1)
+ return $ (_malfunc a2 env (MalList params Nil)
+ (\args -> do
+ fn_env1 <- liftIO $ env_new $ Just env
+ fn_env2 <- liftIO $ env_bind fn_env1 params args
+ eval a2 fn_env2))
+ _ -> throwStr "invalid fn*"
+apply_ast ast@(MalList _ _) env = do
+ mc <- is_macro_call ast env
+ if mc then do
+ new_ast <- macroexpand ast env
+ eval new_ast env
+ else
+ case ast of
+ MalList _ _ -> do
+ el <- eval_ast ast env
+ case el of
+ (MalList ((Func (Fn f) _) : rest) _) ->
+ f $ rest
+ (MalList ((MalFunc {ast=ast,
+ env=fn_env,
+ params=(MalList params Nil)} : rest)) _) -> do
+ fn_env1 <- liftIO $ env_new $ Just fn_env
+ fn_env2 <- liftIO $ env_bind fn_env1 params rest
+ eval ast fn_env2
+ el ->
+ throwStr $ "invalid apply: " ++ (show el)
+ _ -> return ast
+
+eval :: MalVal -> Env -> IOThrows MalVal
+eval ast env = do
+ case ast of
+ (MalList _ _) -> apply_ast ast env
+ _ -> eval_ast ast env
+
+
+-- print
+mal_print :: MalVal -> String
+mal_print exp = show exp
+
+-- repl
+
+rep :: Env -> String -> IOThrows String
+rep env line = do
+ ast <- mal_read line
+ exp <- eval ast env
+ return $ mal_print exp
+
+repl_loop :: Env -> IO ()
+repl_loop env = do
+ line <- readline "user> "
+ case line of
+ Nothing -> return ()
+ Just "" -> repl_loop env
+ Just str -> do
+ res <- runErrorT $ rep env str
+ out <- case res of
+ Left (StringError str) -> return $ "Error: " ++ str
+ Left (MalValError mv) -> return $ "Error: " ++ (show mv)
+ Right val -> return val
+ putStrLn out
+ hFlush stdout
+ repl_loop env
+
+main = do
+ args <- getArgs
+ load_history
+
+ repl_env <- env_new Nothing
+
+ -- core.hs: defined using Haskell
+ (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
+ env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env))
+ env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil)
+
+ -- core.mal: defined using the language itself
+ runErrorT $ rep repl_env "(def! *host-language* \"haskell\")"
+ runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
+ runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+ runErrorT $ rep repl_env "(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)))))))"
+ runErrorT $ rep repl_env "(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 then do
+ env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
+ runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
+ return ()
+ else do
+ runErrorT $ rep repl_env "(println (str \"Mal [\" *host-language* \"]\"))"
+ repl_loop repl_env
diff --git a/java/Makefile b/java/Makefile
index 2e168c4..f242ec0 100644
--- a/java/Makefile
+++ b/java/Makefile
@@ -5,7 +5,7 @@ TESTS =
SOURCES_BASE = src/main/java/mal/readline.java src/main/java/mal/types.java \
src/main/java/mal/reader.java src/main/java/mal/printer.java
SOURCES_LISP = src/main/java/mal/env.java src/main/java/mal/core.java \
- src/main/java/mal/stepA_more.java
+ src/main/java/mal/stepA_mal.java
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
#.PHONY: stats tests $(TESTS)
diff --git a/java/pom.xml b/java/pom.xml
index 2f0a4fd..fa2b567 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -47,7 +47,7 @@
</executions>
<configuration>
<!--
- <mainClass>mal.stepA_more</mainClass>
+ <mainClass>mal.stepA_mal</mainClass>
<arguments>
<argument>foo</argument>
<argument>bar</argument>
@@ -69,7 +69,7 @@
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
- <mainClass>mal.stepA_more</mainClass>
+ <mainClass>mal.stepA_mal</mainClass>
</transformer>
</transformers>
</configuration>
diff --git a/java/src/main/java/mal/core.java b/java/src/main/java/mal/core.java
index 0f1d226..facaeb1 100644
--- a/java/src/main/java/mal/core.java
+++ b/java/src/main/java/mal/core.java
@@ -49,11 +49,32 @@ public class core {
return args.nth(0) == False ? True : False;
}
};
+ static MalFunction symbol = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return new MalSymbol((MalString)args.nth(0));
+ }
+ };
static MalFunction symbol_Q = new MalFunction() {
public MalVal apply(MalList args) throws MalThrowable {
return args.nth(0) instanceof MalSymbol ? True : False;
}
};
+ static MalFunction keyword = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return new MalString(
+ "\u029e" + ((MalString)args.nth(0)).getValue());
+ }
+ };
+ static MalFunction keyword_Q = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ if (args.nth(0) instanceof MalString &&
+ (((MalString)args.nth(0)).getValue().charAt(0) == '\u029e')) {
+ return True;
+ } else {
+ return False;
+ }
+ }
+ };
// String functions
@@ -98,7 +119,7 @@ public class core {
} catch (IOException e) {
throw new MalException(new MalString(e.getMessage()));
} catch (readline.EOFException e) {
- throw new MalException(new MalString(e.getMessage()));
+ return Nil;
}
}
};
@@ -304,7 +325,11 @@ public class core {
static MalFunction count = new MalFunction() {
public MalVal apply(MalList a) throws MalThrowable {
- return new MalInteger(((MalList)a.nth(0)).size());
+ if (a.nth(0) == Nil) {
+ return new MalInteger(0);
+ } else {
+ return new MalInteger(((MalList)a.nth(0)).size());
+ }
}
};
@@ -358,7 +383,11 @@ public class core {
static MalFunction nth = new MalFunction() {
public MalVal apply(MalList a) throws MalThrowable {
Integer idx = ((MalInteger)a.nth(1)).getValue();
- return ((MalList)a.nth(0)).nth(idx);
+ if (idx < ((MalList)a.nth(0)).size()) {
+ return ((MalList)a.nth(0)).nth(idx);
+ } else {
+ throw new MalError("nth: index out of range");
+ }
}
};
@@ -471,7 +500,10 @@ public class core {
.put("nil?", nil_Q)
.put("true?", true_Q)
.put("false?", false_Q)
+ .put("symbol", symbol)
.put("symbol?", symbol_Q)
+ .put("keyword", keyword)
+ .put("keyword?", keyword_Q)
.put("pr-str", pr_str)
.put("str", str)
diff --git a/java/src/main/java/mal/env.java b/java/src/main/java/mal/env.java
index 8a1913e..711a9ee 100644
--- a/java/src/main/java/mal/env.java
+++ b/java/src/main/java/mal/env.java
@@ -30,8 +30,8 @@ public class env {
}
}
- public Env find(String key) {
- if (data.containsKey(key)) {
+ public Env find(MalSymbol key) {
+ if (data.containsKey(key.getName())) {
return this;
} else if (outer != null) {
return outer.find(key);
@@ -40,17 +40,18 @@ public class env {
}
}
- public MalVal get(String key) throws MalThrowable {
+ public MalVal get(MalSymbol key) throws MalThrowable {
Env e = find(key);
if (e == null) {
- throw new MalException("'" + key + "' not found");
+ throw new MalException(
+ "'" + key.getName() + "' not found");
} else {
- return e.data.get(key);
+ return e.data.get(key.getName());
}
}
- public Env set(String key, MalVal value) {
- data.put(key, value);
+ public Env set(MalSymbol key, MalVal value) {
+ data.put(key.getName(), value);
return this;
}
}
diff --git a/java/src/main/java/mal/printer.java b/java/src/main/java/mal/printer.java
index 73dfca3..fe3c5c4 100644
--- a/java/src/main/java/mal/printer.java
+++ b/java/src/main/java/mal/printer.java
@@ -24,7 +24,10 @@ public class printer {
String delim, Boolean print_readably) {
ArrayList<String> strs = new ArrayList<String>();
for (Map.Entry<String, MalVal> entry : value.entrySet()) {
- if (print_readably) {
+ if (entry.getKey().length() > 0 &&
+ entry.getKey().charAt(0) == '\u029e') {
+ strs.add(":" + entry.getKey().substring(1));
+ } else if (print_readably) {
strs.add("\"" + entry.getKey().toString() + "\"");
} else {
strs.add(entry.getKey().toString());
diff --git a/java/src/main/java/mal/reader.java b/java/src/main/java/mal/reader.java
index 6bae506..7c9d3aa 100644
--- a/java/src/main/java/mal/reader.java
+++ b/java/src/main/java/mal/reader.java
@@ -51,7 +51,7 @@ public class reader {
public static MalVal read_atom(Reader rdr)
throws ParseError {
String token = rdr.next();
- Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)");
+ Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)");
Matcher matcher = pattern.matcher(token);
if (!matcher.find()) {
throw new ParseError("unrecognized token '" + token + "'");
@@ -67,7 +67,9 @@ public class reader {
} else if (matcher.group(6) != null) {
return new MalString(StringEscapeUtils.unescapeJson(matcher.group(6)));
} else if (matcher.group(7) != null) {
- return new MalSymbol(matcher.group(7));
+ return new MalString("\u029e" + matcher.group(7));
+ } else if (matcher.group(8) != null) {
+ return new MalSymbol(matcher.group(8));
} else {
throw new ParseError("unrecognized '" + matcher.group(0) + "'");
}
diff --git a/java/src/main/java/mal/step3_env.java b/java/src/main/java/mal/step3_env.java
index a88dc13..d3e221b 100644
--- a/java/src/main/java/mal/step3_env.java
+++ b/java/src/main/java/mal/step3_env.java
@@ -21,8 +21,7 @@ public class step3_env {
// eval
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -65,7 +64,7 @@ public class step3_env {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -76,13 +75,12 @@ public class step3_env {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
return EVAL(a2, let_env);
default:
MalVal args = eval_ast(ast.rest(), env);
- MalSymbol fsym = (MalSymbol)a0;
- ILambda f = (ILambda)env.get(fsym.getName());
+ ILambda f = (ILambda)env.get((MalSymbol)a0);
return f.apply((MalList)args);
}
}
@@ -123,10 +121,10 @@ public class step3_env {
String prompt = "user> ";
Env repl_env = new Env(null);
- repl_env.set("+", add);
- repl_env.set("-", subtract);
- repl_env.set("*", multiply);
- repl_env.set("/", divide);
+ repl_env.set(new MalSymbol("+"), add);
+ repl_env.set(new MalSymbol("-"), subtract);
+ repl_env.set(new MalSymbol("*"), multiply);
+ repl_env.set(new MalSymbol("/"), divide);
if (args.length > 0 && args[0].equals("--raw")) {
readline.mode = readline.Mode.JAVA;
diff --git a/java/src/main/java/mal/step4_if_fn_do.java b/java/src/main/java/mal/step4_if_fn_do.java
index ce9043d..ff15709 100644
--- a/java/src/main/java/mal/step4_if_fn_do.java
+++ b/java/src/main/java/mal/step4_if_fn_do.java
@@ -22,8 +22,7 @@ public class step4_if_fn_do {
// eval
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -64,7 +63,7 @@ public class step4_if_fn_do {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -75,7 +74,7 @@ public class step4_if_fn_do {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
return EVAL(a2, let_env);
case "do":
@@ -130,7 +129,7 @@ public class step4_if_fn_do {
// core.java: defined using Java
for (String key : core.ns.keySet()) {
- repl_env.set(key, core.ns.get(key));
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
}
// core.mal: defined using the language itself
diff --git a/java/src/main/java/mal/step5_tco.java b/java/src/main/java/mal/step5_tco.java
index ef56083..43c87b7 100644
--- a/java/src/main/java/mal/step5_tco.java
+++ b/java/src/main/java/mal/step5_tco.java
@@ -22,8 +22,7 @@ public class step5_tco {
// eval
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -67,7 +66,7 @@ public class step5_tco {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -78,7 +77,7 @@ public class step5_tco {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -143,7 +142,7 @@ public class step5_tco {
// core.java: defined using Java
for (String key : core.ns.keySet()) {
- repl_env.set(key, core.ns.get(key));
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
}
// core.mal: defined using the language itself
diff --git a/java/src/main/java/mal/step6_file.java b/java/src/main/java/mal/step6_file.java
index 56bcdf7..19c4c1c 100644
--- a/java/src/main/java/mal/step6_file.java
+++ b/java/src/main/java/mal/step6_file.java
@@ -22,8 +22,7 @@ public class step6_file {
// eval
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -67,7 +66,7 @@ public class step6_file {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -78,7 +77,7 @@ public class step6_file {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -143,9 +142,9 @@ public class step6_file {
// core.java: defined using Java
for (String key : core.ns.keySet()) {
- repl_env.set(key, core.ns.get(key));
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
}
- repl_env.set("eval", new MalFunction() {
+ repl_env.set(new MalSymbol("eval"), new MalFunction() {
public MalVal apply(MalList args) throws MalThrowable {
return EVAL(args.nth(0), repl_env);
}
@@ -154,7 +153,7 @@ public class step6_file {
for (Integer i=1; i < args.length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
diff --git a/java/src/main/java/mal/step7_quote.java b/java/src/main/java/mal/step7_quote.java
index 8c3766a..6d015b4 100644
--- a/java/src/main/java/mal/step7_quote.java
+++ b/java/src/main/java/mal/step7_quote.java
@@ -49,8 +49,7 @@ public class step7_quote {
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -94,7 +93,7 @@ public class step7_quote {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -105,7 +104,7 @@ public class step7_quote {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -175,9 +174,9 @@ public class step7_quote {
// core.java: defined using Java
for (String key : core.ns.keySet()) {
- repl_env.set(key, core.ns.get(key));
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
}
- repl_env.set("eval", new MalFunction() {
+ repl_env.set(new MalSymbol("eval"), new MalFunction() {
public MalVal apply(MalList args) throws MalThrowable {
return EVAL(args.nth(0), repl_env);
}
@@ -186,7 +185,7 @@ public class step7_quote {
for (Integer i=1; i < args.length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
diff --git a/java/src/main/java/mal/step8_macros.java b/java/src/main/java/mal/step8_macros.java
index 4c89356..38a4aef 100644
--- a/java/src/main/java/mal/step8_macros.java
+++ b/java/src/main/java/mal/step8_macros.java
@@ -52,8 +52,8 @@ public class step8_macros {
if (ast instanceof MalList) {
MalVal a0 = ((MalList)ast).nth(0);
if (a0 instanceof MalSymbol &&
- env.find(((MalSymbol)a0).getName()) != null) {
- MalVal mac = env.get(((MalSymbol)a0).getName());
+ env.find(((MalSymbol)a0)) != null) {
+ MalVal mac = env.get(((MalSymbol)a0));
if (mac instanceof MalFunction &&
((MalFunction)mac).isMacro()) {
return true;
@@ -67,7 +67,7 @@ public class step8_macros {
throws MalThrowable {
while (is_macro_call(ast, env)) {
MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0);
- MalFunction mac = (MalFunction) env.get(a0.getName());
+ MalFunction mac = (MalFunction) env.get(a0);
ast = mac.apply(((MalList)ast).rest());
}
return ast;
@@ -75,8 +75,7 @@ public class step8_macros {
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -122,7 +121,7 @@ public class step8_macros {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -133,7 +132,7 @@ public class step8_macros {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -148,7 +147,7 @@ public class step8_macros {
a2 = ast.nth(2);
res = EVAL(a2, env);
((MalFunction)res).setMacro();
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "macroexpand":
a1 = ast.nth(1);
@@ -213,9 +212,9 @@ public class step8_macros {
// core.java: defined using Java
for (String key : core.ns.keySet()) {
- repl_env.set(key, core.ns.get(key));
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
}
- repl_env.set("eval", new MalFunction() {
+ repl_env.set(new MalSymbol("eval"), new MalFunction() {
public MalVal apply(MalList args) throws MalThrowable {
return EVAL(args.nth(0), repl_env);
}
@@ -224,7 +223,7 @@ public class step8_macros {
for (Integer i=1; i < args.length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
diff --git a/java/src/main/java/mal/step9_try.java b/java/src/main/java/mal/step9_try.java
new file mode 100644
index 0000000..ceeff27
--- /dev/null
+++ b/java/src/main/java/mal/step9_try.java
@@ -0,0 +1,299 @@
+package mal;
+
+import java.io.IOException;
+
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+import mal.printer;
+import mal.env.Env;
+import mal.core;
+
+public class step9_try {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static Boolean is_pair(MalVal x) {
+ return x instanceof MalList && ((MalList)x).size() > 0;
+ }
+
+ public static MalVal quasiquote(MalVal ast) {
+ if (!is_pair(ast)) {
+ return new MalList(new MalSymbol("quote"), ast);
+ } else {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if ((a0 instanceof MalSymbol) &&
+ (((MalSymbol)a0).getName() == "unquote")) {
+ return ((MalList)ast).nth(1);
+ } else if (is_pair(a0)) {
+ MalVal a00 = ((MalList)a0).nth(0);
+ if ((a00 instanceof MalSymbol) &&
+ (((MalSymbol)a00).getName() == "splice-unquote")) {
+ return new MalList(new MalSymbol("concat"),
+ ((MalList)a0).nth(1),
+ quasiquote(((MalList)ast).rest()));
+ }
+ }
+ return new MalList(new MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(((MalList)ast).rest()));
+ }
+ }
+
+ public static Boolean is_macro_call(MalVal ast, Env env)
+ throws MalThrowable {
+ if (ast instanceof MalList) {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if (a0 instanceof MalSymbol &&
+ env.find(((MalSymbol)a0)) != null) {
+ MalVal mac = env.get(((MalSymbol)a0));
+ if (mac instanceof MalFunction &&
+ ((MalFunction)mac).isMacro()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static MalVal macroexpand(MalVal ast, Env env)
+ throws MalThrowable {
+ while (is_macro_call(ast, env)) {
+ MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0);
+ MalFunction mac = (MalFunction) env.get(a0);
+ ast = mac.apply(((MalList)ast).rest());
+ }
+ return ast;
+ }
+
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ return env.get((MalSymbol)ast);
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = ast.list_Q() ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a0, a1,a2, a3, res;
+ MalList el;
+
+ while (true) {
+
+ //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+ if (!orig_ast.list_Q()) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalVal expanded = macroexpand(orig_ast, env);
+ if (!expanded.list_Q()) { return expanded; }
+ MalList ast = (MalList) expanded;
+ if (ast.size() == 0) { return ast; }
+ a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key, EVAL(val, let_env));
+ }
+ orig_ast = a2;
+ env = let_env;
+ break;
+ case "quote":
+ return ast.nth(1);
+ case "quasiquote":
+ orig_ast = quasiquote(ast.nth(1));
+ break;
+ case "defmacro!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ ((MalFunction)res).setMacro();
+ env.set((MalSymbol)a1, res);
+ return res;
+ case "macroexpand":
+ a1 = ast.nth(1);
+ return macroexpand(a1, env);
+ case "try*":
+ try {
+ return EVAL(ast.nth(1), env);
+ } catch (Throwable t) {
+ if (ast.size() > 2) {
+ MalVal exc;
+ a2 = ast.nth(2);
+ MalVal a20 = ((MalList)a2).nth(0);
+ if (((MalSymbol)a20).getName().equals("catch*")) {
+ if (t instanceof MalException) {
+ exc = ((MalException)t).getValue();
+ } else {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ String tstr = sw.toString();
+ exc = new MalString(t.getMessage() + ": " + tstr);
+ }
+ return EVAL(((MalList)a2).nth(2),
+ new Env(env, ((MalList)a2).slice(1,2),
+ new MalList(exc)));
+ }
+ }
+ throw t;
+ }
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast.nth(ast.size()-1);
+ break;
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast.nth(3);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast.nth(2);
+ }
+ break;
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction (a2f, (mal.env.Env)env, a1f) {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = f.genEnv(el.slice(1));
+ } else {
+ return f.apply(el.rest());
+ }
+ }
+
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return printer._pr_str(exp, true);
+ }
+
+ // repl
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ final Env repl_env = new Env(null);
+
+ // core.java: defined using Java
+ for (String key : core.ns.keySet()) {
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
+ }
+ repl_env.set(new MalSymbol("eval"), new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(args.nth(0), repl_env);
+ }
+ });
+ MalList _argv = new MalList();
+ for (Integer i=1; i < args.length; i++) {
+ _argv.conj_BANG(new MalString(args[i]));
+ }
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
+
+
+ // core.mal: defined using the language itself
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+ RE(repl_env, "(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)))))))");
+ RE(repl_env, "(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))))))))");
+
+ Integer fileIdx = 0;
+ if (args.length > 0 && args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ fileIdx = 1;
+ }
+ if (args.length > fileIdx) {
+ RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
+ return;
+ }
+
+ // repl loop
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (MalException e) {
+ System.out.println("Error: " + printer._pr_str(e.getValue(), false));
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ } catch (Throwable t) {
+ System.out.println("Uncaught " + t + ": " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/stepA_more.java b/java/src/main/java/mal/stepA_mal.java
index 7e869e6..f8d4056 100644
--- a/java/src/main/java/mal/stepA_more.java
+++ b/java/src/main/java/mal/stepA_mal.java
@@ -15,7 +15,7 @@ import mal.printer;
import mal.env.Env;
import mal.core;
-public class stepA_more {
+public class stepA_mal {
// read
public static MalVal READ(String str) throws MalThrowable {
return reader.read_str(str);
@@ -54,8 +54,8 @@ public class stepA_more {
if (ast instanceof MalList) {
MalVal a0 = ((MalList)ast).nth(0);
if (a0 instanceof MalSymbol &&
- env.find(((MalSymbol)a0).getName()) != null) {
- MalVal mac = env.get(((MalSymbol)a0).getName());
+ env.find(((MalSymbol)a0)) != null) {
+ MalVal mac = env.get(((MalSymbol)a0));
if (mac instanceof MalFunction &&
((MalFunction)mac).isMacro()) {
return true;
@@ -69,7 +69,7 @@ public class stepA_more {
throws MalThrowable {
while (is_macro_call(ast, env)) {
MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0);
- MalFunction mac = (MalFunction) env.get(a0.getName());
+ MalFunction mac = (MalFunction) env.get(a0);
ast = mac.apply(((MalList)ast).rest());
}
return ast;
@@ -77,8 +77,7 @@ public class stepA_more {
public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
if (ast instanceof MalSymbol) {
- MalSymbol sym = (MalSymbol)ast;
- return env.get(sym.getName());
+ return env.get((MalSymbol)ast);
} else if (ast instanceof MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
@@ -124,7 +123,7 @@ public class stepA_more {
a1 = ast.nth(1);
a2 = ast.nth(2);
res = EVAL(a2, env);
- env.set(((MalSymbol)a1).getName(), res);
+ env.set(((MalSymbol)a1), res);
return res;
case "let*":
a1 = ast.nth(1);
@@ -135,7 +134,7 @@ public class stepA_more {
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1).nth(i);
val = ((MalList)a1).nth(i+1);
- let_env.set(key.getName(), EVAL(val, let_env));
+ let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
@@ -150,7 +149,7 @@ public class stepA_more {
a2 = ast.nth(2);
res = EVAL(a2, env);
((MalFunction)res).setMacro();
- env.set(((MalSymbol)a1).getName(), res);
+ env.set((MalSymbol)a1, res);
return res;
case "macroexpand":
a1 = ast.nth(1);
@@ -239,9 +238,9 @@ public class stepA_more {
// core.java: defined using Java
for (String key : core.ns.keySet()) {
- repl_env.set(key, core.ns.get(key));
+ repl_env.set(new MalSymbol(key), core.ns.get(key));
}
- repl_env.set("eval", new MalFunction() {
+ repl_env.set(new MalSymbol("eval"), new MalFunction() {
public MalVal apply(MalList args) throws MalThrowable {
return EVAL(args.nth(0), repl_env);
}
@@ -250,7 +249,7 @@ public class stepA_more {
for (Integer i=1; i < args.length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
- repl_env.set("*ARGV*", _argv);
+ repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
diff --git a/java/src/main/java/mal/types.java b/java/src/main/java/mal/types.java
index 7ad419a..a8a2dfa 100644
--- a/java/src/main/java/mal/types.java
+++ b/java/src/main/java/mal/types.java
@@ -134,6 +134,7 @@ public class types {
public static class MalSymbol extends MalVal {
String value;
public MalSymbol(String v) { value = v; }
+ public MalSymbol(MalString v) { value = v.getValue(); }
public MalSymbol copy() throws MalThrowable { return this; }
public String getName() { return value; }
@@ -152,7 +153,9 @@ public class types {
return "\"" + value + "\"";
}
public String toString(Boolean print_readably) {
- if (print_readably) {
+ if (value.length() > 0 && value.charAt(0) == '\u029e') {
+ return ":" + value.substring(1);
+ } else if (print_readably) {
return "\"" + printer.escapeString(value) + "\"";
} else {
return value;
diff --git a/js/Makefile b/js/Makefile
index 454e481..98c4291 100644
--- a/js/Makefile
+++ b/js/Makefile
@@ -2,7 +2,7 @@
TESTS = tests/types.js tests/reader.js
SOURCES_BASE = node_readline.js types.js reader.js printer.js
-SOURCES_LISP = env.js core.js stepA_more.js
+SOURCES_LISP = env.js core.js stepA_mal.js
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
WEB_SOURCES = $(SOURCES:node_readline.js=jq_readline.js)
diff --git a/js/core.js b/js/core.js
index 3ab2117..d2be63b 100644
--- a/js/core.js
+++ b/js/core.js
@@ -95,7 +95,10 @@ function concat(lst) {
return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1));
}
-function nth(lst, idx) { return lst[idx]; }
+function nth(lst, idx) {
+ if (idx < lst.length) { return lst[idx]; }
+ else { throw new Error("nth: index out of range"); }
+}
function first(lst) { return lst[0]; }
@@ -105,7 +108,8 @@ function empty_Q(lst) { return lst.length === 0; }
function count(s) {
if (Array.isArray(s)) { return s.length; }
- else { return Object.keys(s).length; }
+ else if (s === null) { return 0; }
+ else { return Object.keys(s).length; }
}
function conj(lst) {
@@ -165,6 +169,8 @@ var ns = {'type': types._obj_type,
'false?': types._false_Q,
'symbol': types._symbol,
'symbol?': types._symbol_Q,
+ 'keyword': types._keyword,
+ 'keyword?': types._keyword_Q,
'pr-str': pr_str,
'str': str,
diff --git a/js/env.js b/js/env.js
index 3c9eac8..421b220 100644
--- a/js/env.js
+++ b/js/env.js
@@ -26,15 +26,27 @@ function Env(outer, binds, exprs) {
return this;
}
Env.prototype.find = function (key) {
- if (key in this.data) { return this; }
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.find key must be a symbol")
+ }
+ if (key.value in this.data) { return this; }
else if (this.outer) { return this.outer.find(key); }
else { return null; }
};
-Env.prototype.set = function(key, value) { this.data[key] = value; return value; },
+Env.prototype.set = function(key, value) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.set key must be a symbol")
+ }
+ this.data[key.value] = value;
+ return value;
+};
Env.prototype.get = function(key) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.get key must be a symbol")
+ }
var env = this.find(key);
- if (!env) { throw new Error("'" + key + "' not found"); }
- return env.data[key];
+ if (!env) { throw new Error("'" + key.value + "' not found"); }
+ return env.data[key.value];
};
exports.Env = env.Env = Env;
diff --git a/js/interop.js b/js/interop.js
new file mode 100644
index 0000000..89da4a6
--- /dev/null
+++ b/js/interop.js
@@ -0,0 +1,36 @@
+// Node vs browser behavior
+var interop = {};
+if (typeof module === 'undefined') {
+ var exports = interop,
+ GLOBAL = window;
+}
+
+function resolve_js(str) {
+ if (str.match(/\./)) {
+ var re = /^(.*)\.\([^\.]*)$/,
+ match = re.exec(str);
+ return [eval(match[0]), eval(str)];
+ } else {
+ return [GLOBAL, eval(str)];
+ }
+}
+
+function js_to_mal(obj) {
+ var cache = [];
+ var str = JSON.stringify(obj, function(key, value) {
+ if (typeof value === 'object' && value !== null) {
+ if (cache.indexOf(value) !== -1) {
+ // Circular reference found, discard key
+ return;
+ }
+ // Store value in our collection
+ cache.push(value);
+ }
+ return value;
+ });
+ cache = null; // Enable garbage collection
+ return JSON.parse(str);
+}
+
+exports.resolve_js = interop.resolve_js = resolve_js;
+exports.js_to_mal = interop.js_to_mal = js_to_mal;
diff --git a/js/printer.js b/js/printer.js
index f3836e0..4f267e7 100644
--- a/js/printer.js
+++ b/js/printer.js
@@ -26,13 +26,17 @@ function _pr_str(obj, print_readably) {
}
return "{" + ret.join(' ') + "}";
case 'string':
- if (_r) {
- return '"' + obj.replace(/\\/, "\\\\")
+ if (obj[0] === '\u029e') {
+ return ':' + obj.slice(1);
+ } else if (_r) {
+ return '"' + obj.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n") + '"'; // string
} else {
return obj;
}
+ case 'keyword':
+ return ':' + obj.slice(1);
case 'nil':
return "nil";
case 'atom':
diff --git a/js/reader.js b/js/reader.js
index 3f2f6ca..dd4de9a 100644
--- a/js/reader.js
+++ b/js/reader.js
@@ -35,6 +35,8 @@ function read_atom (reader) {
return token.slice(1,token.length-1)
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n"); // string
+ } else if (token[0] === ":") {
+ return types._keyword(token.slice(1));
} else if (token === "nil") {
return null;
} else if (token === "true") {
diff --git a/js/step3_env.js b/js/step3_env.js
index 1f5efb7..ca8f818 100644
--- a/js/step3_env.js
+++ b/js/step3_env.js
@@ -47,7 +47,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
return EVAL(a2, let_env);
default:
@@ -70,10 +70,10 @@ function PRINT(exp) {
var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
-repl_env.set('+', function(a,b){return a+b;});
-repl_env.set('-', function(a,b){return a-b;});
-repl_env.set('*', function(a,b){return a*b;});
-repl_env.set('/', function(a,b){return a/b;});
+repl_env.set(types._symbol('+'), function(a,b){return a+b;});
+repl_env.set(types._symbol('-'), function(a,b){return a-b;});
+repl_env.set(types._symbol('*'), function(a,b){return a*b;});
+repl_env.set(types._symbol('/'), function(a,b){return a/b;});
// repl loop
if (typeof require !== 'undefined' && require.main === module) {
diff --git a/js/step4_if_fn_do.js b/js/step4_if_fn_do.js
index 27715fa..937d0ea 100644
--- a/js/step4_if_fn_do.js
+++ b/js/step4_if_fn_do.js
@@ -48,7 +48,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
return EVAL(a2, let_env);
case "do":
@@ -86,7 +86,7 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/js/step5_tco.js b/js/step5_tco.js
index 659ac40..03de2cc 100644
--- a/js/step5_tco.js
+++ b/js/step5_tco.js
@@ -50,7 +50,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -97,7 +97,7 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/js/step6_file.js b/js/step6_file.js
index 4c8ed17..813c66d 100644
--- a/js/step6_file.js
+++ b/js/step6_file.js
@@ -50,7 +50,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -97,9 +97,10 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/js/step7_quote.js b/js/step7_quote.js
index 6259672..b39ebbd 100644
--- a/js/step7_quote.js
+++ b/js/step7_quote.js
@@ -70,7 +70,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -122,9 +122,10 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/js/step8_macros.js b/js/step8_macros.js
index f51592b..397379e 100644
--- a/js/step8_macros.js
+++ b/js/step8_macros.js
@@ -36,8 +36,8 @@ function quasiquote(ast) {
function is_macro_call(ast, env) {
return types._list_Q(ast) &&
types._symbol_Q(ast[0]) &&
- env.find(ast[0].value) &&
- env.get(ast[0].value)._ismacro_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -88,7 +88,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -146,9 +146,10 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
@@ -157,7 +158,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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 (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
diff --git a/js/step9_interop.js b/js/step9_try.js
index 89d2ca4..6be4474 100644
--- a/js/step9_interop.js
+++ b/js/step9_try.js
@@ -36,8 +36,8 @@ function quasiquote(ast) {
function is_macro_call(ast, env) {
return types._list_Q(ast) &&
types._symbol_Q(ast[0]) &&
- env.find(ast[0].value) &&
- env.get(ast[0].value)._ismacro_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -88,7 +88,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -104,12 +104,17 @@ function _EVAL(ast, env) {
return env.set(a1, func);
case 'macroexpand':
return macroexpand(a1, env);
- case "js*":
- return eval(a1.toString());
- case ".":
- var el = eval_ast(ast.slice(2), env),
- f = eval(a1.toString());
- return f.apply(f, el);
+ case "try*":
+ try {
+ return EVAL(a1, env);
+ } catch (exc) {
+ if (a2 && a2[0].value === "catch*") {
+ if (exc instanceof Error) { exc = exc.message; }
+ return EVAL(a2[2], new Env(env, [a2[1]], [exc]));
+ } else {
+ throw exc;
+ }
+ }
case "do":
eval_ast(ast.slice(1, -1), env);
ast = ast[ast.length-1];
@@ -152,9 +157,10 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
@@ -163,7 +169,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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 (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
diff --git a/js/stepA_more.js b/js/stepA_mal.js
index 0955b7f..d879cd3 100644
--- a/js/stepA_more.js
+++ b/js/stepA_mal.js
@@ -5,6 +5,7 @@ if (typeof module !== 'undefined') {
var printer = require('./printer');
var Env = require('./env').Env;
var core = require('./core');
+ var interop = require('./interop');
}
// read
@@ -36,8 +37,8 @@ function quasiquote(ast) {
function is_macro_call(ast, env) {
return types._list_Q(ast) &&
types._symbol_Q(ast[0]) &&
- env.find(ast[0].value) &&
- env.get(ast[0].value)._ismacro_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -88,7 +89,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -108,8 +109,11 @@ function _EVAL(ast, env) {
return eval(a1.toString());
case ".":
var el = eval_ast(ast.slice(2), env),
- f = eval(a1.toString());
- return f.apply(f, el);
+ r = interop.resolve_js(a1.toString()),
+ obj = r[0], f = r[1];
+ var res = f.apply(obj, el);
+ console.log("DEBUG3:", res);
+ return interop.js_to_mal(res);
case "try*":
try {
return EVAL(a1, env);
@@ -163,9 +167,10 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
// core.mal: defined using the language itself
rep("(def! *host-language* \"javascript\")")
@@ -175,7 +180,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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 (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
diff --git a/js/tests/stepA_mal.mal b/js/tests/stepA_mal.mal
new file mode 100644
index 0000000..f785292
--- /dev/null
+++ b/js/tests/stepA_mal.mal
@@ -0,0 +1,24 @@
+;; Testing basic bash interop
+
+(js* "7")
+;=>7
+
+(js* "'7'")
+;=>"7"
+
+(js* "[7,8,9]")
+;=>(7 8 9)
+
+(js* "console.log('hello');")
+; hello
+;=>nil
+
+(js* "foo=8;")
+(js* "foo;")
+;=>8
+
+(js* "['a','b','c'].map(function(x){return 'X'+x+'Y'}).join(' ')")
+;=>"XaY XbY XcY"
+
+(js* "[1,2,3].map(function(x){return 1+x})")
+;=>(2 3 4)
diff --git a/js/types.js b/js/types.js
index d288231..e3901b7 100644
--- a/js/types.js
+++ b/js/types.js
@@ -4,7 +4,7 @@ if (typeof module === 'undefined') {
var exports = types;
}
-// General fucnctions
+// General functions
function _obj_type(obj) {
if (_symbol_Q(obj)) { return 'symbol'; }
@@ -19,7 +19,7 @@ function _obj_type(obj) {
switch (typeof(obj)) {
case 'number': return 'number';
case 'function': return 'function';
- case 'string': return 'string';
+ case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string';
default: throw new Error("Unknown type '" + typeof(obj) + "'");
}
}
@@ -79,6 +79,10 @@ function _clone (obj) {
default:
throw new Error("clone of non-collection: " + _obj_type(obj));
}
+ Object.defineProperty(new_obj, "__meta__", {
+ enumerable: false,
+ writable: true
+ });
return new_obj;
}
@@ -99,6 +103,13 @@ function _symbol(name) { return new Symbol(name); }
function _symbol_Q(obj) { return obj instanceof Symbol; }
+// Keywords
+function _keyword(name) { return "\u029e" + name; }
+function _keyword_Q(obj) {
+ return typeof obj === 'string' && obj[0] === '\u029e';
+}
+
+
// Functions
function _function(Eval, Env, ast, env, params) {
var fn = function() {
@@ -148,6 +159,7 @@ function _hash_map_Q(hm) {
return typeof hm === "object" &&
!Array.isArray(hm) &&
!(hm === null) &&
+ !(hm instanceof Symbol) &&
!(hm instanceof Atom);
}
function _assoc_BANG(hm) {
@@ -157,10 +169,6 @@ function _assoc_BANG(hm) {
for (var i=1; i<arguments.length; i+=2) {
var ktoken = arguments[i],
vtoken = arguments[i+1];
- // TODO: support more than string keys
- //if (list_Q(ktoken) && hash_map_Q(ktoken)) {
- // throw new Error("expected hash-map key atom, got collection");
- //}
if (typeof ktoken !== "string") {
throw new Error("expected hash-map key string, got: " + (typeof ktoken));
}
@@ -193,6 +201,8 @@ exports._true_Q = types._true_Q = _true_Q;
exports._false_Q = types._false_Q = _false_Q;
exports._symbol = types._symbol = _symbol;
exports._symbol_Q = types._symbol_Q = _symbol_Q;
+exports._keyword = types._keyword = _keyword;
+exports._keyword_Q = types._keyword_Q = _keyword_Q;
exports._function = types._function = _function;
exports._function_Q = types._function_Q = _function_Q;
exports._list = types._list = _list;
diff --git a/js/web/mal.js b/js/web/mal.js
index 62d7a02..13db245 100644
--- a/js/web/mal.js
+++ b/js/web/mal.js
@@ -27,7 +27,7 @@ if (typeof module === 'undefined') {
var exports = types;
}
-// General fucnctions
+// General functions
function _obj_type(obj) {
if (_symbol_Q(obj)) { return 'symbol'; }
@@ -42,7 +42,7 @@ function _obj_type(obj) {
switch (typeof(obj)) {
case 'number': return 'number';
case 'function': return 'function';
- case 'string': return 'string';
+ case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string';
default: throw new Error("Unknown type '" + typeof(obj) + "'");
}
}
@@ -102,6 +102,10 @@ function _clone (obj) {
default:
throw new Error("clone of non-collection: " + _obj_type(obj));
}
+ Object.defineProperty(new_obj, "__meta__", {
+ enumerable: false,
+ writable: true
+ });
return new_obj;
}
@@ -122,6 +126,13 @@ function _symbol(name) { return new Symbol(name); }
function _symbol_Q(obj) { return obj instanceof Symbol; }
+// Keywords
+function _keyword(name) { return "\u029e" + name; }
+function _keyword_Q(obj) {
+ return typeof obj === 'string' && obj[0] === '\u029e';
+}
+
+
// Functions
function _function(Eval, Env, ast, env, params) {
var fn = function() {
@@ -171,6 +182,7 @@ function _hash_map_Q(hm) {
return typeof hm === "object" &&
!Array.isArray(hm) &&
!(hm === null) &&
+ !(hm instanceof Symbol) &&
!(hm instanceof Atom);
}
function _assoc_BANG(hm) {
@@ -180,10 +192,6 @@ function _assoc_BANG(hm) {
for (var i=1; i<arguments.length; i+=2) {
var ktoken = arguments[i],
vtoken = arguments[i+1];
- // TODO: support more than string keys
- //if (list_Q(ktoken) && hash_map_Q(ktoken)) {
- // throw new Error("expected hash-map key atom, got collection");
- //}
if (typeof ktoken !== "string") {
throw new Error("expected hash-map key string, got: " + (typeof ktoken));
}
@@ -216,6 +224,8 @@ exports._true_Q = types._true_Q = _true_Q;
exports._false_Q = types._false_Q = _false_Q;
exports._symbol = types._symbol = _symbol;
exports._symbol_Q = types._symbol_Q = _symbol_Q;
+exports._keyword = types._keyword = _keyword;
+exports._keyword_Q = types._keyword_Q = _keyword_Q;
exports._function = types._function = _function;
exports._function_Q = types._function_Q = _function_Q;
exports._list = types._list = _list;
@@ -264,6 +274,8 @@ function read_atom (reader) {
return token.slice(1,token.length-1)
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n"); // string
+ } else if (token[0] === ":") {
+ return types._keyword(token.slice(1));
} else if (token === "nil") {
return null;
} else if (token === "true") {
@@ -383,13 +395,17 @@ function _pr_str(obj, print_readably) {
}
return "{" + ret.join(' ') + "}";
case 'string':
- if (_r) {
- return '"' + obj.replace(/\\/, "\\\\")
+ if (obj[0] === '\u029e') {
+ return ':' + obj.slice(1);
+ } else if (_r) {
+ return '"' + obj.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n") + '"'; // string
} else {
return obj;
}
+ case 'keyword':
+ return ':' + obj.slice(1);
case 'nil':
return "nil";
case 'atom':
@@ -429,15 +445,27 @@ function Env(outer, binds, exprs) {
return this;
}
Env.prototype.find = function (key) {
- if (key in this.data) { return this; }
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.find key must be a symbol")
+ }
+ if (key.value in this.data) { return this; }
else if (this.outer) { return this.outer.find(key); }
else { return null; }
};
-Env.prototype.set = function(key, value) { this.data[key] = value; return value; },
+Env.prototype.set = function(key, value) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.set key must be a symbol")
+ }
+ this.data[key.value] = value;
+ return value;
+};
Env.prototype.get = function(key) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.get key must be a symbol")
+ }
var env = this.find(key);
- if (!env) { throw new Error("'" + key + "' not found"); }
- return env.data[key];
+ if (!env) { throw new Error("'" + key.value + "' not found"); }
+ return env.data[key.value];
};
exports.Env = env.Env = Env;
@@ -534,7 +562,10 @@ function concat(lst) {
return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1));
}
-function nth(lst, idx) { return lst[idx]; }
+function nth(lst, idx) {
+ if (idx < lst.length) { return lst[idx]; }
+ else { throw new Error("nth: index out of range"); }
+}
function first(lst) { return lst[0]; }
@@ -544,7 +575,8 @@ function empty_Q(lst) { return lst.length === 0; }
function count(s) {
if (Array.isArray(s)) { return s.length; }
- else { return Object.keys(s).length; }
+ else if (s === null) { return 0; }
+ else { return Object.keys(s).length; }
}
function conj(lst) {
@@ -604,6 +636,8 @@ var ns = {'type': types._obj_type,
'false?': types._false_Q,
'symbol': types._symbol,
'symbol?': types._symbol_Q,
+ 'keyword': types._keyword,
+ 'keyword?': types._keyword_Q,
'pr-str': pr_str,
'str': str,
@@ -688,8 +722,8 @@ function quasiquote(ast) {
function is_macro_call(ast, env) {
return types._list_Q(ast) &&
types._symbol_Q(ast[0]) &&
- env.find(ast[0].value) &&
- env.get(ast[0].value)._ismacro_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -740,7 +774,7 @@ function _EVAL(ast, env) {
case "let*":
var let_env = new Env(env);
for (var i=0; i < a1.length; i+=2) {
- let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -760,8 +794,11 @@ function _EVAL(ast, env) {
return eval(a1.toString());
case ".":
var el = eval_ast(ast.slice(2), env),
- f = eval(a1.toString());
- return f.apply(f, el);
+ r = interop.resolve_js(a1.toString()),
+ obj = r[0], f = r[1];
+ var res = f.apply(obj, el);
+ console.log("DEBUG3:", res);
+ return interop.js_to_mal(res);
case "try*":
try {
return EVAL(a1, env);
@@ -815,9 +852,10 @@ var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
// core.js: defined using javascript
-for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
// core.mal: defined using the language itself
rep("(def! *host-language* \"javascript\")")
@@ -827,7 +865,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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 (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
diff --git a/lua/Makefile b/lua/Makefile
new file mode 100644
index 0000000..169c587
--- /dev/null
+++ b/lua/Makefile
@@ -0,0 +1,26 @@
+TESTS =
+
+SOURCES_BASE = utils.lua types.lua reader.lua printer.lua
+SOURCES_LISP = env.lua core.lua stepA_mal.lua
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+all: libs
+
+.PHONY: stats tests $(TESTS)
+
+clean:
+ rm -f linenoise.so
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
+
+
+.PHONY: libs
+libs: linenoise.so
+
+linenoise.so:
+ luarocks install --tree=./ linenoise
+ ln -sf lib/lua/5.1/linenoise.so $@
+
diff --git a/lua/core.lua b/lua/core.lua
new file mode 100644
index 0000000..279a6d6
--- /dev/null
+++ b/lua/core.lua
@@ -0,0 +1,228 @@
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local readline = require('readline')
+
+local Nil, List, _pr_str = types.Nil, types.List, printer._pr_str
+
+local M = {}
+
+-- string functions
+
+function pr_str(...)
+ return table.concat(
+ utils.map(function(e) return _pr_str(e, true) end, arg), " ")
+end
+
+function str(...)
+ return table.concat(
+ utils.map(function(e) return _pr_str(e, false) end, arg), "")
+end
+
+function prn(...)
+ print(table.concat(
+ utils.map(function(e) return _pr_str(e, true) end, arg), " "))
+ io.flush()
+ return Nil
+end
+
+function println(...)
+ print(table.concat(
+ utils.map(function(e) return _pr_str(e, false) end, arg), " "))
+ io.flush()
+ return Nil
+end
+
+function slurp(file)
+ local lines = {}
+ for line in io.lines(file) do
+ lines[#lines+1] = line
+ end
+ return table.concat(lines, "\n") .. "\n"
+end
+
+function do_readline(prompt)
+ local line = readline.readline(prompt)
+ if line == nil then
+ return Nil
+ else
+ return line
+ end
+end
+
+-- hash map functions
+
+function assoc(hm, ...)
+ return types._assoc_BANG(types.copy(hm), unpack(arg))
+end
+
+function dissoc(hm, ...)
+ return types._dissoc_BANG(types.copy(hm), unpack(arg))
+end
+
+function get(hm, key)
+ local res = hm[key]
+ if res == nil then return Nil end
+ return res
+end
+
+function keys(hm)
+ local res = {}
+ for k,v in pairs(hm) do
+ res[#res+1] = k
+ end
+ return List:new(res)
+end
+
+function vals(hm)
+ local res = {}
+ for k,v in pairs(hm) do
+ res[#res+1] = v
+ end
+ return List:new(res)
+end
+
+-- sequential functions
+
+function cons(a,lst)
+ local new_lst = lst:slice(1)
+ table.insert(new_lst, 1, a)
+ return List:new(new_lst)
+end
+
+function concat(...)
+ local new_lst = {}
+ for i = 1, #arg do
+ for j = 1, #arg[i] do
+ table.insert(new_lst, arg[i][j])
+ end
+ end
+ return List:new(new_lst)
+end
+
+function nth(seq, idx)
+ if idx+1 <= #seq then
+ return seq[idx+1]
+ else
+ types.throw("nth: index out of range")
+ end
+end
+
+function first(a)
+ if #a == 0 then
+ return Nil
+ else
+ return a[1]
+ end
+end
+
+function apply(f, ...)
+ if types._malfunc_Q(f) then
+ f = f.fn
+ end
+ local args = concat(types.slice(arg, 1, #arg-1),
+ arg[#arg])
+ return f(unpack(args))
+end
+
+function map(f, lst)
+ if types._malfunc_Q(f) then
+ f = f.fn
+ end
+ return List:new(utils.map(f, lst))
+end
+
+-- metadata functions
+
+function meta(obj)
+ local m = getmetatable(obj)
+ if m == nil or m.meta == nil then return Nil end
+ return m.meta
+end
+
+function with_meta(obj, meta)
+ local new_obj = types.copy(obj)
+ getmetatable(new_obj).meta = meta
+ return new_obj
+end
+
+-- atom functions
+
+function swap_BANG(atm,f,...)
+ if types._malfunc_Q(f) then
+ f = f.fn
+ end
+ local args = List:new(arg)
+ table.insert(args, 1, atm.val)
+ atm.val = f(unpack(args))
+ return atm.val
+end
+
+M.ns = {
+ ['='] = types._equal_Q,
+ throw = types.throw,
+
+ ['nil?'] = function(a) return a==Nil end,
+ ['true?'] = function(a) return a==true end,
+ ['false?'] = function(a) return a==false end,
+ symbol = function(a) return types.Symbol:new(a) end,
+ ['symbol?'] = function(a) return types._symbol_Q(a) end,
+ keyword = function(a) return "\177"..a end,
+ ['keyword?'] = function(a) return types._keyword_Q(a) end,
+
+ ['pr-str'] = pr_str,
+ str = str,
+ prn = prn,
+ println = println,
+ ['read-string'] = reader.read_str,
+ readline = do_readline,
+ slurp = slurp,
+
+ ['<'] = function(a,b) return a<b end,
+ ['<='] = function(a,b) return a<=b end,
+ ['>'] = function(a,b) return a>b end,
+ ['>='] = function(a,b) return a>=b end,
+ ['+'] = function(a,b) return a+b end,
+ ['-'] = function(a,b) return a-b end,
+ ['*'] = function(a,b) return a*b end,
+ ['/'] = function(a,b) return math.floor(a/b) end,
+ -- TODO: get actual milliseconds
+ ['time-ms'] = function() return os.time() * 1000 end,
+
+ list = function(...) return List:new(arg) end,
+ ['list?'] = function(a) return types._list_Q(a) end,
+ vector = function(...) return types.Vector:new(arg) end,
+ ['vector?'] = types._vector_Q,
+ ['hash-map'] = types.hash_map,
+ ['map?'] = types._hash_map_Q,
+ assoc = assoc,
+ dissoc = dissoc,
+ get = get,
+ ['contains?'] = function(a,b) return a[b] ~= nil end,
+ keys = keys,
+ vals = vals,
+
+ ['sequential?'] = types._sequential_Q,
+ cons = cons,
+ concat = concat,
+ nth = nth,
+ first = first,
+ rest = function(a) return List:new(a:slice(2)) end,
+ ['empty?'] = function(a) return a==Nil or #a == 0 end,
+ count = function(a) return #a end,
+ apply = apply,
+ map = map,
+ conj = function(...) return Nil end,
+
+ meta = meta,
+ ['with-meta'] = with_meta,
+ atom = function(a) return types.Atom:new(a) end,
+ ['atom?'] = types._atom_Q,
+ deref = function(a) return a.val end,
+ ['reset!'] = function(a,b) a.val = b; return b end,
+ ['swap!'] = swap_BANG,
+}
+
+return M
+
diff --git a/lua/env.lua b/lua/env.lua
new file mode 100644
index 0000000..ee19c90
--- /dev/null
+++ b/lua/env.lua
@@ -0,0 +1,53 @@
+local rex = require('rex_pcre')
+local string = require('string')
+local table = require('table')
+local utils = require('utils')
+local types = require('types')
+
+local Env = {}
+
+function Env:new(outer, binds, exprs)
+ local data = {}
+ local newObj = {outer = outer, data = data}
+ self.__index = self
+ if binds then
+ for i, b in ipairs(binds) do
+ if binds[i].val == '&' then
+ local new_exprs = types.List:new()
+ for j = i, #exprs do
+ table.insert(new_exprs, exprs[j])
+ end
+ table.remove(exprs, 1)
+ data[binds[i+1].val] = new_exprs
+ break
+ end
+ data[binds[i].val] = exprs[i]
+ end
+ end
+ return setmetatable(newObj, self)
+end
+function Env:find(sym)
+ if self.data[sym.val] ~= nil then
+ return self
+ else
+ if self.outer ~= nil then
+ return self.outer:find(sym)
+ else
+ return nil
+ end
+ end
+end
+function Env:set(sym,val)
+ self.data[sym.val] = val
+ return val
+end
+function Env:get(sym)
+ local env = self:find(sym)
+ if env then
+ return env.data[sym.val]
+ else
+ types.throw("'"..sym.val.."' not found")
+ end
+end
+
+return Env
diff --git a/lua/printer.lua b/lua/printer.lua
new file mode 100644
index 0000000..8c1cdad
--- /dev/null
+++ b/lua/printer.lua
@@ -0,0 +1,55 @@
+local string = require('string')
+local table = require('table')
+local types = require('types')
+local utils = require('utils')
+
+local M = {}
+
+function M._pr_str(obj, print_readably)
+ local _r = print_readably
+ if utils.instanceOf(obj, types.Symbol) then
+ return obj.val
+ elseif types._list_Q(obj) then
+ return "(" .. table.concat(utils.map(function(e)
+ return M._pr_str(e,_r) end, obj), " ") .. ")"
+ elseif types._vector_Q(obj) then
+ return "[" .. table.concat(utils.map(function(e)
+ return M._pr_str(e,_r) end, obj), " ") .. "]"
+ elseif types._hash_map_Q(obj) then
+ local res = {}
+ for k,v in pairs(obj) do
+ res[#res+1] = M._pr_str(k, _r)
+ res[#res+1] = M._pr_str(v, _r)
+ end
+ return "{".. table.concat(res, " ").."}"
+ elseif type(obj) == 'string' then
+ if string.sub(obj,1,1) == "\177" then
+ return ':' .. string.sub(obj,2)
+ else
+ if _r then
+ local sval = obj:gsub('\\', '\\\\')
+ sval = sval:gsub('"', '\\"')
+ sval = sval:gsub('\n', '\\n')
+ return '"' .. sval .. '"'
+ else
+ return obj
+ end
+ end
+ elseif obj == types.Nil then
+ return "nil"
+ elseif obj == true then
+ return "true"
+ elseif obj == false then
+ return "false"
+ elseif types._malfunc_Q(obj) then
+ return "(fn* "..M._pr_str(obj.params).." "..M._pr_str(obj.ast)..")"
+ elseif types._atom_Q(obj) then
+ return "(atom "..M._pr_str(obj.val)..")"
+ elseif type(obj) == 'function' then
+ return "#<function>"
+ else
+ return string.format("%s", obj)
+ end
+end
+
+return M
diff --git a/lua/reader.lua b/lua/reader.lua
new file mode 100644
index 0000000..2d29a52
--- /dev/null
+++ b/lua/reader.lua
@@ -0,0 +1,127 @@
+local rex = require('rex_pcre')
+local string = require('string')
+local table = require('table')
+local types = require('types')
+local throw, Nil, Symbol, List = types.throw, types.Nil,
+ types.Symbol, types.List
+
+local M = {}
+
+Reader = {}
+function Reader:new(tokens)
+ local newObj = {tokens = tokens, position = 1}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function Reader:next()
+ self.position = self.position + 1
+ return self.tokens[self.position-1]
+end
+function Reader:peek()
+ return self.tokens[self.position]
+end
+
+function M.tokenize(str)
+ local results = {}
+ local re_pos = 1
+ local re = rex.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)", rex.flags().EXTENDED)
+ while true do
+ local s, e, t = re:exec(str, re_pos)
+ if not s or s > e then break end
+ re_pos = e + 1
+ local val = string.sub(str,t[1],t[2])
+ if string.sub(val,1,1) ~= ";" then
+ table.insert(results, val)
+ end
+ end
+ return results
+end
+
+function M.read_atom(rdr)
+ local int_re = rex.new("^-?[0-9]+$")
+ local float_re = rex.new("^-?[0-9][0-9.]*$")
+ local token = rdr:next()
+ if int_re:exec(token) then return tonumber(token)
+ elseif float_re:exec(token) then return tonumber(token)
+ elseif string.sub(token,1,1) == '"' then
+ local sval = string.sub(token,2,string.len(token)-1)
+ sval = string.gsub(sval, '\\"', '"')
+ sval = string.gsub(sval, '\\n', '\n')
+ return sval
+ elseif string.sub(token,1,1) == ':' then
+ return "\177" .. string.sub(token,2)
+ elseif token == "nil" then return Nil
+ elseif token == "true" then return true
+ elseif token == "false" then return false
+ else return Symbol:new(token)
+ end
+end
+
+function M.read_sequence(rdr, start, last)
+ local ast = {}
+ local token = rdr:next()
+ if token ~= start then throw("expected '"..start.."'") end
+
+ token = rdr:peek()
+ while token ~= last do
+ if not token then throw("expected '"..last.."', got EOF") end
+ table.insert(ast, M.read_form(rdr))
+ token = rdr:peek()
+ end
+ rdr:next()
+ return ast
+end
+
+function M.read_list(rdr)
+ return types.List:new(M.read_sequence(rdr, '(', ')'))
+end
+
+function M.read_vector(rdr)
+ return types.Vector:new(M.read_sequence(rdr, '[', ']'))
+end
+
+function M.read_hash_map(rdr)
+ local seq = M.read_sequence(rdr, '{', '}')
+ return types._assoc_BANG(types.HashMap:new(), unpack(seq))
+end
+
+function M.read_form(rdr)
+ local token = rdr:peek()
+
+ if "'" == token then
+ rdr:next()
+ return List:new({Symbol:new('quote'), M.read_form(rdr)})
+ elseif '`' == token then
+ rdr:next()
+ return List:new({Symbol:new('quasiquote'), M.read_form(rdr)})
+ elseif '~' == token then
+ rdr:next()
+ return List:new({Symbol:new('unquote'), M.read_form(rdr)})
+ elseif '~@' == token then
+ rdr:next()
+ return List:new({Symbol:new('splice-unquote'), M.read_form(rdr)})
+ elseif '^' == token then
+ rdr:next()
+ local meta = M.read_form(rdr)
+ return List:new({Symbol:new('with-meta'), M.read_form(rdr), meta})
+ elseif '@' == token then
+ rdr:next()
+ return List:new({Symbol:new('deref'), M.read_form(rdr)})
+
+ elseif ')' == token then throw("unexpected ')'")
+ elseif '(' == token then return M.read_list(rdr)
+ elseif ']' == token then throw("unexpected ']'")
+ elseif '[' == token then return M.read_vector(rdr)
+ elseif '}' == token then throw("unexpected '}'")
+ elseif '{' == token then return M.read_hash_map(rdr)
+ else return M.read_atom(rdr)
+ end
+end
+
+function M.read_str(str)
+ local tokens = M.tokenize(str)
+ if #tokens == 0 then error(nil) end
+ return M.read_form(Reader:new(tokens))
+end
+
+return M
diff --git a/lua/readline.lua b/lua/readline.lua
new file mode 100644
index 0000000..5acdb54
--- /dev/null
+++ b/lua/readline.lua
@@ -0,0 +1,33 @@
+local LN = require('linenoise')
+
+local M = {}
+
+local history_loaded = false
+local history_file = os.getenv("HOME") .. "/.mal-history"
+
+M.raw = false
+
+function M.readline(prompt)
+ if not history_loaded then
+ history_loaded = true
+ for line in io.lines(history_file) do
+ LN.historyadd(line)
+ end
+ end
+
+ if M.raw then
+ io.write(prompt); io.flush();
+ line = io.read()
+ else
+ line = LN.linenoise(prompt)
+ end
+ if line then
+ LN.historyadd(line)
+ local f = io.open(history_file, "a")
+ f:write(line.."\n")
+ f:close()
+ end
+ return line
+end
+
+return M
diff --git a/lua/step0_repl.lua b/lua/step0_repl.lua
new file mode 100755
index 0000000..24584d2
--- /dev/null
+++ b/lua/step0_repl.lua
@@ -0,0 +1,25 @@
+#!/usr/bin/env lua
+
+local readline = require('readline')
+
+function READ(str)
+ return str
+end
+
+function EVAL(ast, any)
+ return ast
+end
+
+function PRINT(exp)
+ return exp
+end
+
+function rep(str)
+ return PRINT(EVAL(READ(str),""))
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ print(rep(line))
+end
diff --git a/lua/step1_read_print.lua b/lua/step1_read_print.lua
new file mode 100755
index 0000000..46d71f5
--- /dev/null
+++ b/lua/step1_read_print.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env lua
+
+local readline = require('readline')
+local utils = require('utils')
+local reader = require('reader')
+local printer = require('printer')
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function EVAL(ast, env)
+ return ast
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+function rep(str)
+ return PRINT(EVAL(READ(str),""))
+end
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step2_eval.lua b/lua/step2_eval.lua
new file mode 100755
index 0000000..22ac8cf
--- /dev/null
+++ b/lua/step2_eval.lua
@@ -0,0 +1,79 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ if env[ast.val] == nil then
+ types.throw("'"..ast.val.."' not found")
+ end
+ return env[ast.val]
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ return f(unpack(args))
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = {['+'] = function(a,b) return a+b end,
+ ['-'] = function(a,b) return a-b end,
+ ['*'] = function(a,b) return a*b end,
+ ['/'] = function(a,b) return math.floor(a/b) end}
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step3_env.lua b/lua/step3_env.lua
new file mode 100755
index 0000000..dbdb879
--- /dev/null
+++ b/lua/step3_env.lua
@@ -0,0 +1,92 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ local a0,a1,a2 = ast[1], ast[2],ast[3]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ return EVAL(a2, let_env)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ return f(unpack(args))
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+repl_env:set(types.Symbol:new('+'), function(a,b) return a+b end)
+repl_env:set(types.Symbol:new('-'), function(a,b) return a-b end)
+repl_env:set(types.Symbol:new('*'), function(a,b) return a*b end)
+repl_env:set(types.Symbol:new('/'), function(a,b) return math.floor(a/b) end)
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step4_if_fn_do.lua b/lua/step4_if_fn_do.lua
new file mode 100755
index 0000000..65b3a0a
--- /dev/null
+++ b/lua/step4_if_fn_do.lua
@@ -0,0 +1,110 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ return EVAL(a2, let_env)
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast), env)
+ return el[#el]
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then return EVAL(a3, env) else return types.Nil end
+ else
+ return EVAL(a2, env)
+ end
+ elseif 'fn*' == a0sym then
+ return function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ return f(unpack(args))
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+
+-- core.mal: defined using mal
+rep("(def! not (fn* (a) (if a false true)))")
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step5_tco.lua b/lua/step5_tco.lua
new file mode 100755
index 0000000..237f5ea
--- /dev/null
+++ b/lua/step5_tco.lua
@@ -0,0 +1,118 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ while true do
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ env = let_env
+ ast = a2 -- TCO
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast-1), env)
+ ast = ast[#ast] -- TCO
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then ast = a3 else return types.Nil end -- TCO
+ else
+ ast = a2 -- TCO
+ end
+ elseif 'fn*' == a0sym then
+ return types.MalFunc:new(function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end, a2, env, a1)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ if types._malfunc_Q(f) then
+ ast = f.ast
+ env = Env:new(f.env, f.params, args) -- TCO
+ else
+ return f(unpack(args))
+ end
+ end
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+
+-- core.mal: defined using mal
+rep("(def! not (fn* (a) (if a false true)))")
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step6_file.lua b/lua/step6_file.lua
new file mode 100755
index 0000000..7992888
--- /dev/null
+++ b/lua/step6_file.lua
@@ -0,0 +1,128 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ while true do
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ env = let_env
+ ast = a2 -- TCO
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast-1), env)
+ ast = ast[#ast] -- TCO
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then ast = a3 else return types.Nil end -- TCO
+ else
+ ast = a2 -- TCO
+ end
+ elseif 'fn*' == a0sym then
+ return types.MalFunc:new(function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end, a2, env, a1)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ if types._malfunc_Q(f) then
+ ast = f.ast
+ env = Env:new(f.env, f.params, args) -- TCO
+ else
+ return f(unpack(args))
+ end
+ end
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+repl_env:set(types.Symbol:new('eval'),
+ function(ast) return EVAL(ast, repl_env) end)
+repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2)))
+
+-- core.mal: defined using mal
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+ table.remove(arg,1)
+end
+
+if #arg > 0 then
+ rep("(load-file \""..arg[1].."\")")
+ os.exit(0)
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step7_quote.lua b/lua/step7_quote.lua
new file mode 100755
index 0000000..32bf60c
--- /dev/null
+++ b/lua/step7_quote.lua
@@ -0,0 +1,154 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function is_pair(x)
+ return types._sequential_Q(x) and #x > 0
+end
+
+function quasiquote(ast)
+ if not is_pair(ast) then
+ return types.List:new({types.Symbol:new("quote"), ast})
+ elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then
+ return ast[2]
+ elseif is_pair(ast[1]) and
+ types._symbol_Q(ast[1][1]) and
+ ast[1][1].val == 'splice-unquote' then
+ return types.List:new({types.Symbol:new("concat"),
+ ast[1][2],
+ quasiquote(ast:slice(2))})
+ else
+ return types.List:new({types.Symbol:new("cons"),
+ quasiquote(ast[1]),
+ quasiquote(ast:slice(2))})
+ end
+end
+
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ while true do
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ env = let_env
+ ast = a2 -- TCO
+ elseif 'quote' == a0sym then
+ return a1
+ elseif 'quasiquote' == a0sym then
+ ast = quasiquote(a1) -- TCO
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast-1), env)
+ ast = ast[#ast] -- TCO
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then ast = a3 else return types.Nil end -- TCO
+ else
+ ast = a2 -- TCO
+ end
+ elseif 'fn*' == a0sym then
+ return types.MalFunc:new(function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end, a2, env, a1)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ if types._malfunc_Q(f) then
+ ast = f.ast
+ env = Env:new(f.env, f.params, args) -- TCO
+ else
+ return f(unpack(args))
+ end
+ end
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+repl_env:set(types.Symbol:new('eval'),
+ function(ast) return EVAL(ast, repl_env) end)
+repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2)))
+
+-- core.mal: defined using mal
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+ table.remove(arg,1)
+end
+
+if #arg > 0 then
+ rep("(load-file \""..arg[1].."\")")
+ os.exit(0)
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step8_macros.lua b/lua/step8_macros.lua
new file mode 100755
index 0000000..25a9238
--- /dev/null
+++ b/lua/step8_macros.lua
@@ -0,0 +1,183 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function is_pair(x)
+ return types._sequential_Q(x) and #x > 0
+end
+
+function quasiquote(ast)
+ if not is_pair(ast) then
+ return types.List:new({types.Symbol:new("quote"), ast})
+ elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then
+ return ast[2]
+ elseif is_pair(ast[1]) and
+ types._symbol_Q(ast[1][1]) and
+ ast[1][1].val == 'splice-unquote' then
+ return types.List:new({types.Symbol:new("concat"),
+ ast[1][2],
+ quasiquote(ast:slice(2))})
+ else
+ return types.List:new({types.Symbol:new("cons"),
+ quasiquote(ast[1]),
+ quasiquote(ast:slice(2))})
+ end
+end
+
+function is_macro_call(ast, env)
+ if types._list_Q(ast) and
+ types._symbol_Q(ast[1]) and
+ env:find(ast[1]) then
+ local f = env:get(ast[1])
+ return types._malfunc_Q(f) and f.ismacro
+ end
+end
+
+function macroexpand(ast, env)
+ while is_macro_call(ast, env) do
+ local mac = env:get(ast[1])
+ ast = mac.fn(unpack(ast:slice(2)))
+ end
+ return ast
+end
+
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ while true do
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ -- apply list
+ ast = macroexpand(ast, env)
+ if not types._list_Q(ast) then return ast end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ env = let_env
+ ast = a2 -- TCO
+ elseif 'quote' == a0sym then
+ return a1
+ elseif 'quasiquote' == a0sym then
+ ast = quasiquote(a1) -- TCO
+ elseif 'defmacro!' == a0sym then
+ local mac = EVAL(a2, env)
+ mac.ismacro = true
+ return env:set(a1, mac)
+ elseif 'macroexpand' == a0sym then
+ return macroexpand(a1, env)
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast-1), env)
+ ast = ast[#ast] -- TCO
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then ast = a3 else return types.Nil end -- TCO
+ else
+ ast = a2 -- TCO
+ end
+ elseif 'fn*' == a0sym then
+ return types.MalFunc:new(function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end, a2, env, a1)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ if types._malfunc_Q(f) then
+ ast = f.ast
+ env = Env:new(f.env, f.params, args) -- TCO
+ else
+ return f(unpack(args))
+ end
+ end
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+repl_env:set(types.Symbol:new('eval'),
+ function(ast) return EVAL(ast, repl_env) end)
+repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2)))
+
+-- core.mal: defined using mal
+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 #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+ table.remove(arg,1)
+end
+
+if #arg > 0 then
+ rep("(load-file \""..arg[1].."\")")
+ os.exit(0)
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function()
+ print(rep(line))
+ end, function(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+ end)
+end
diff --git a/lua/step9_try.lua b/lua/step9_try.lua
new file mode 100755
index 0000000..315d698
--- /dev/null
+++ b/lua/step9_try.lua
@@ -0,0 +1,203 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function is_pair(x)
+ return types._sequential_Q(x) and #x > 0
+end
+
+function quasiquote(ast)
+ if not is_pair(ast) then
+ return types.List:new({types.Symbol:new("quote"), ast})
+ elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then
+ return ast[2]
+ elseif is_pair(ast[1]) and
+ types._symbol_Q(ast[1][1]) and
+ ast[1][1].val == 'splice-unquote' then
+ return types.List:new({types.Symbol:new("concat"),
+ ast[1][2],
+ quasiquote(ast:slice(2))})
+ else
+ return types.List:new({types.Symbol:new("cons"),
+ quasiquote(ast[1]),
+ quasiquote(ast:slice(2))})
+ end
+end
+
+function is_macro_call(ast, env)
+ if types._list_Q(ast) and
+ types._symbol_Q(ast[1]) and
+ env:find(ast[1]) then
+ local f = env:get(ast[1])
+ return types._malfunc_Q(f) and f.ismacro
+ end
+end
+
+function macroexpand(ast, env)
+ while is_macro_call(ast, env) do
+ local mac = env:get(ast[1])
+ ast = mac.fn(unpack(ast:slice(2)))
+ end
+ return ast
+end
+
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ while true do
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ -- apply list
+ ast = macroexpand(ast, env)
+ if not types._list_Q(ast) then return ast end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ env = let_env
+ ast = a2 -- TCO
+ elseif 'quote' == a0sym then
+ return a1
+ elseif 'quasiquote' == a0sym then
+ ast = quasiquote(a1) -- TCO
+ elseif 'defmacro!' == a0sym then
+ local mac = EVAL(a2, env)
+ mac.ismacro = true
+ return env:set(a1, mac)
+ elseif 'macroexpand' == a0sym then
+ return macroexpand(a1, env)
+ elseif 'try*' == a0sym then
+ local exc, result = nil, nil
+ xpcall(function()
+ result = EVAL(a1, env)
+ end, function(err)
+ exc = err
+ end)
+ if exc ~= nil then
+ if types._malexception_Q(exc) then
+ exc = exc.val
+ end
+ if a2 and a2[1].val == 'catch*' then
+ result = EVAL(a2[3], Env:new(env, {a2[2]}, {exc}))
+ else
+ types.throw(exc)
+ end
+ end
+ return result
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast-1), env)
+ ast = ast[#ast] -- TCO
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then ast = a3 else return types.Nil end -- TCO
+ else
+ ast = a2 -- TCO
+ end
+ elseif 'fn*' == a0sym then
+ return types.MalFunc:new(function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end, a2, env, a1)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ if types._malfunc_Q(f) then
+ ast = f.ast
+ env = Env:new(f.env, f.params, args) -- TCO
+ else
+ return f(unpack(args))
+ end
+ end
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+repl_env:set(types.Symbol:new('eval'),
+ function(ast) return EVAL(ast, repl_env) end)
+repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2)))
+
+-- core.mal: defined using mal
+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))))))))")
+
+function print_exception(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+end
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+ table.remove(arg,1)
+end
+
+if #arg > 0 then
+ xpcall(function() rep("(load-file \""..arg[1].."\")") end,
+ print_exception)
+ os.exit(0)
+end
+
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function() print(rep(line)) end,
+ print_exception)
+end
diff --git a/lua/stepA_mal.lua b/lua/stepA_mal.lua
new file mode 100755
index 0000000..db31789
--- /dev/null
+++ b/lua/stepA_mal.lua
@@ -0,0 +1,205 @@
+#!/usr/bin/env lua
+
+local table = require('table')
+
+local readline = require('readline')
+local utils = require('utils')
+local types = require('types')
+local reader = require('reader')
+local printer = require('printer')
+local Env = require('env')
+local core = require('core')
+local List, Vector, HashMap = types.List, types.Vector, types.HashMap
+
+-- read
+function READ(str)
+ return reader.read_str(str)
+end
+
+-- eval
+function is_pair(x)
+ return types._sequential_Q(x) and #x > 0
+end
+
+function quasiquote(ast)
+ if not is_pair(ast) then
+ return types.List:new({types.Symbol:new("quote"), ast})
+ elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then
+ return ast[2]
+ elseif is_pair(ast[1]) and
+ types._symbol_Q(ast[1][1]) and
+ ast[1][1].val == 'splice-unquote' then
+ return types.List:new({types.Symbol:new("concat"),
+ ast[1][2],
+ quasiquote(ast:slice(2))})
+ else
+ return types.List:new({types.Symbol:new("cons"),
+ quasiquote(ast[1]),
+ quasiquote(ast:slice(2))})
+ end
+end
+
+function is_macro_call(ast, env)
+ if types._list_Q(ast) and
+ types._symbol_Q(ast[1]) and
+ env:find(ast[1]) then
+ local f = env:get(ast[1])
+ return types._malfunc_Q(f) and f.ismacro
+ end
+end
+
+function macroexpand(ast, env)
+ while is_macro_call(ast, env) do
+ local mac = env:get(ast[1])
+ ast = mac.fn(unpack(ast:slice(2)))
+ end
+ return ast
+end
+
+function eval_ast(ast, env)
+ if types._symbol_Q(ast) then
+ return env:get(ast)
+ elseif types._list_Q(ast) then
+ return List:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._vector_Q(ast) then
+ return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast))
+ elseif types._hash_map_Q(ast) then
+ local new_hm = {}
+ for k,v in pairs(ast) do
+ new_hm[EVAL(k, env)] = EVAL(v, env)
+ end
+ return HashMap:new(new_hm)
+ else
+ return ast
+ end
+end
+
+function EVAL(ast, env)
+ while true do
+ --print("EVAL: "..printer._pr_str(ast,true))
+ if not types._list_Q(ast) then return eval_ast(ast, env) end
+
+ -- apply list
+ ast = macroexpand(ast, env)
+ if not types._list_Q(ast) then return ast end
+
+ local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4]
+ local a0sym = types._symbol_Q(a0) and a0.val or ""
+ if 'def!' == a0sym then
+ return env:set(a1, EVAL(a2, env))
+ elseif 'let*' == a0sym then
+ local let_env = Env:new(env)
+ for i = 1,#a1,2 do
+ let_env:set(a1[i], EVAL(a1[i+1], let_env))
+ end
+ env = let_env
+ ast = a2 -- TCO
+ elseif 'quote' == a0sym then
+ return a1
+ elseif 'quasiquote' == a0sym then
+ ast = quasiquote(a1) -- TCO
+ elseif 'defmacro!' == a0sym then
+ local mac = EVAL(a2, env)
+ mac.ismacro = true
+ return env:set(a1, mac)
+ elseif 'macroexpand' == a0sym then
+ return macroexpand(a1, env)
+ elseif 'try*' == a0sym then
+ local exc, result = nil, nil
+ xpcall(function()
+ result = EVAL(a1, env)
+ end, function(err)
+ exc = err
+ end)
+ if exc ~= nil then
+ if types._malexception_Q(exc) then
+ exc = exc.val
+ end
+ if a2 and a2[1].val == 'catch*' then
+ result = EVAL(a2[3], Env:new(env, {a2[2]}, {exc}))
+ else
+ types.throw(exc)
+ end
+ end
+ return result
+ elseif 'do' == a0sym then
+ local el = eval_ast(ast:slice(2,#ast-1), env)
+ ast = ast[#ast] -- TCO
+ elseif 'if' == a0sym then
+ local cond = EVAL(a1, env)
+ if cond == types.Nil or cond == false then
+ if a3 then ast = a3 else return types.Nil end -- TCO
+ else
+ ast = a2 -- TCO
+ end
+ elseif 'fn*' == a0sym then
+ return types.MalFunc:new(function(...)
+ return EVAL(a2, Env:new(env, a1, arg))
+ end, a2, env, a1)
+ else
+ local args = eval_ast(ast, env)
+ local f = table.remove(args, 1)
+ if types._malfunc_Q(f) then
+ ast = f.ast
+ env = Env:new(f.env, f.params, args) -- TCO
+ else
+ return f(unpack(args))
+ end
+ end
+ end
+end
+
+-- print
+function PRINT(exp)
+ return printer._pr_str(exp, true)
+end
+
+-- repl
+local repl_env = Env:new()
+function rep(str)
+ return PRINT(EVAL(READ(str),repl_env))
+end
+
+-- core.lua: defined using Lua
+for k,v in pairs(core.ns) do
+ repl_env:set(types.Symbol:new(k), v)
+end
+repl_env:set(types.Symbol:new('eval'),
+ function(ast) return EVAL(ast, repl_env) end)
+repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2)))
+
+-- core.mal: defined using mal
+rep("(def! *host-language* \"lua\")")
+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))))))))")
+
+function print_exception(exc)
+ if exc then
+ if types._malexception_Q(exc) then
+ exc = printer._pr_str(exc.val, true)
+ end
+ print("Error: " .. exc)
+ print(debug.traceback())
+ end
+end
+
+if #arg > 0 and arg[1] == "--raw" then
+ readline.raw = true
+ table.remove(arg,1)
+end
+
+if #arg > 0 then
+ xpcall(function() rep("(load-file \""..arg[1].."\")") end,
+ print_exception)
+ os.exit(0)
+end
+
+rep("(println (str \"Mal [\" *host-language* \"]\"))")
+while true do
+ line = readline.readline("user> ")
+ if not line then break end
+ xpcall(function() print(rep(line)) end,
+ print_exception)
+end
diff --git a/lua/types.lua b/lua/types.lua
new file mode 100644
index 0000000..23a003b
--- /dev/null
+++ b/lua/types.lua
@@ -0,0 +1,193 @@
+local utils = require('utils')
+
+local M = {}
+
+-- type functions
+
+function M._sequential_Q(obj)
+ return M._list_Q(obj) or M._vector_Q(obj)
+end
+
+function M._equal_Q(a,b)
+ if M._symbol_Q(a) and M._symbol_Q(b) then
+ return a.val == b.val
+ elseif M._sequential_Q(a) and M._sequential_Q(b) then
+ if #a ~= #b then return false end
+ for i, v in ipairs(a) do
+ if not M._equal_Q(v,b[i]) then return false end
+ end
+ return true
+ else
+ return a == b
+ end
+end
+
+function M.copy(obj)
+ if type(obj) ~= "table" then return obj end
+
+ -- copy object data
+ local new_obj = {}
+ for k,v in pairs(obj) do
+ new_obj[k] = v
+ end
+
+ -- copy metatable and link to original
+ local old_mt = getmetatable(obj)
+ if old_mt ~= nil then
+ local new_mt = {}
+ for k,v in pairs(old_mt) do
+ new_mt[k] = v
+ end
+ setmetatable(new_mt, old_mt)
+ setmetatable(new_obj, new_mt)
+ end
+
+ return new_obj
+end
+
+function M.slice(lst, start, last)
+ if last == nil then last = #lst end
+ local new_lst = {}
+ if start <= last then
+ for i = start, last do
+ new_lst[#new_lst+1] = lst[i]
+ end
+ end
+ return new_lst
+end
+
+-- Error/exceptions
+
+M.MalException = {}
+function M.MalException:new(val)
+ local newObj = {val = val}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M._malexception_Q(obj)
+ return utils.instanceOf(obj, M.MalException)
+end
+
+function M.throw(val)
+ error(M.MalException:new(val))
+end
+
+-- Nil
+
+local NilType = {}
+function NilType:new(val)
+ local newObj = {}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+M.Nil = NilType:new()
+function M._nil_Q(obj)
+ return obj == Nil
+end
+
+-- Strings
+function M._string_Q(obj)
+ return type(obj) == "string"
+end
+
+-- Symbols
+
+M.Symbol = {}
+function M.Symbol:new(val)
+ local newObj = {val = val}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M._symbol_Q(obj)
+ return utils.instanceOf(obj, M.Symbol)
+end
+
+-- Keywords
+function M._keyword_Q(obj)
+ return M._string_Q(obj) and "\177" == string.sub(obj,1,1)
+end
+
+
+-- Lists
+
+M.List = {}
+function M.List:new(lst)
+ local newObj = lst and lst or {}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M._list_Q(obj)
+ return utils.instanceOf(obj, M.List)
+end
+function M.List:slice(start,last)
+ return M.List:new(M.slice(self,start,last))
+end
+
+-- Vectors
+
+M.Vector = {}
+function M.Vector:new(lst)
+ local newObj = lst and lst or {}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M._vector_Q(obj)
+ return utils.instanceOf(obj, M.Vector)
+end
+function M.Vector:slice(start,last)
+ return M.Vector:new(M.slice(self,start,last))
+end
+
+-- Hash Maps
+--
+M.HashMap = {}
+function M.HashMap:new(val)
+ local newObj = val and val or {}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M.hash_map(...)
+ return M._assoc_BANG(M.HashMap:new(), unpack(arg))
+end
+function M._hash_map_Q(obj)
+ return utils.instanceOf(obj, M.HashMap)
+end
+function M._assoc_BANG(hm, ...)
+ for i = 1, #arg, 2 do
+ hm[arg[i]] = arg[i+1]
+ end
+ return hm
+end
+function M._dissoc_BANG(hm, ...)
+ for i = 1, #arg do
+ hm[arg[i]] = nil
+ end
+ return hm
+end
+
+-- Functions
+
+M.MalFunc = {}
+function M.MalFunc:new(fn, ast, env, params)
+ local newObj = {fn = fn, ast = ast, env = env,
+ params = params, ismacro = false}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M._malfunc_Q(obj)
+ return utils.instanceOf(obj, M.MalFunc)
+end
+
+-- Atoms
+
+M.Atom = {}
+function M.Atom:new(val)
+ local newObj = {val = val}
+ self.__index = self
+ return setmetatable(newObj, self)
+end
+function M._atom_Q(obj)
+ return utils.instanceOf(obj, M.Atom)
+end
+
+return M
diff --git a/lua/utils.lua b/lua/utils.lua
new file mode 100644
index 0000000..1ed03e1
--- /dev/null
+++ b/lua/utils.lua
@@ -0,0 +1,53 @@
+local M = {}
+
+function M.try(f, catch_f)
+ local status, exception = pcall(f)
+ if not status then
+ catch_f(exception)
+ end
+end
+
+function M.instanceOf(subject, super)
+ super = tostring(super)
+ local mt = getmetatable(subject)
+
+ while true do
+ if mt == nil then return false end
+ if tostring(mt) == super then return true end
+ mt = getmetatable(mt)
+ end
+end
+
+--[[
+function M.isArray(o)
+ local i = 0
+ for _ in pairs(o) do
+ i = i + 1
+ if o[i] == nil then return false end
+ end
+ return true
+end
+]]--
+
+function M.map(func, obj)
+ local new_obj = {}
+ for i,v in ipairs(obj) do
+ new_obj[i] = func(v)
+ end
+ return new_obj
+end
+
+function M.dump(o)
+ if type(o) == 'table' then
+ local s = '{ '
+ for k,v in pairs(o) do
+ if type(k) ~= 'number' then k = '"'..k..'"' end
+ s = s .. '['..k..'] = ' .. M.dump(v) .. ','
+ end
+ return s .. '} '
+ else
+ return tostring(o)
+ end
+end
+
+return M
diff --git a/make/Makefile b/make/Makefile
index 52a7a7d..70dea08 100644
--- a/make/Makefile
+++ b/make/Makefile
@@ -1,8 +1,9 @@
-TESTS = tests/types.mk tests/reader.mk tests/step9_interop.mk
+TESTS = tests/types.mk tests/reader.mk tests/stepA_mal.mk
-SOURCES_BASE = util.mk readline.mk gmsl.mk types.mk reader.mk printer.mk
-SOURCES_LISP = env.mk core.mk stepA_more.mk
+SOURCES_BASE = util.mk numbers.mk readline.mk gmsl.mk types.mk \
+ reader.mk printer.mk
+SOURCES_LISP = env.mk core.mk stepA_mal.mk
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
mal.mk: $(SOURCES)
diff --git a/make/core.mk b/make/core.mk
index 2ee6597..c2cef59 100644
--- a/make/core.mk
+++ b/make/core.mk
@@ -32,24 +32,28 @@ false? = $(if $(call _false?,$(1)),$(__true),$(__false))
# Symbol functions
+symbol = $(call _symbol,$(call str_decode,$($(1)_value)))
symbol? = $(if $(call _symbol?,$(1)),$(__true),$(__false))
+# Keyword functions
+keyword = $(call _keyword,$(call str_decode,$($(1)_value)))
+keyword? = $(if $(call _keyword?,$(1)),$(__true),$(__false))
+
# Number functions
number? = $(if $(call _number?,$(1)),$(__true),$(__false))
-number_lt = $(if $(call int_lt,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
-number_lte = $(if $(call int_lte,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
-number_gt = $(if $(call int_gt,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
-number_gte = $(if $(call int_gte,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
-
-number_plus = $(call _pnumber,$(call int_plus,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
-number_subtract = $(call _pnumber,$(call int_subtract,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
-number_multiply = $(call _pnumber,$(call int_multiply,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
-number_divide = $(call _pnumber,$(call int_divide,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_lt = $(if $(call int_lt_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+number_lte = $(if $(call int_lte_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+number_gt = $(if $(call int_gt_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+number_gte = $(if $(call int_gte_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
-time_secs = $(call _number,$(shell echo $$(( $$(date +%s) % 65536 ))))
+number_plus = $(call _pnumber,$(call int_add_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_subtract = $(call _pnumber,$(call int_sub_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_multiply = $(call _pnumber,$(call int_mult_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_divide = $(call _pnumber,$(call int_div_encoded,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+time_ms = $(call _number,$(shell echo $$(date +%s%3N)))
# String functions
@@ -60,12 +64,12 @@ str = $(call _string,$(call _pr_str_mult,$(1),,))
prn = $(info $(call _pr_str_mult,$(1),yes, ))
println = $(info $(subst \n,$(NEWLINE),$(call _pr_str_mult,$(1),, )))
-readline= $(foreach res,$(call _string,$(call READLINE,"$(call str_decode,$($(1)_value))")),$(if $(READLINE_EOF),$(__nil),$(res)))
+readline= $(foreach res,$(call _string,$(call READLINE,"$(call str_decode,$($(1)_value))")),$(if $(READLINE_EOF),$(eval READLINE_EOF :=)$(__nil),$(res)))
read_str= $(call READ_STR,$(1))
slurp = $(call _string,$(call _read_file,$(call str_decode,$($(1)_value))))
subs = $(strip \
- $(foreach start,$(call gmsl_plus,1,$(call int_decode,$($(word 2,$(1))_value))),\
+ $(foreach start,$(call int_add,1,$(call int_decode,$($(word 2,$(1))_value))),\
$(foreach end,$(if $(3),$(call int_decode,$($(3)_value)),$(words $($(word 1,$(1))_value))),\
$(call _string,$(wordlist $(start),$(end),$($(word 1,$(1))_value))))))
@@ -98,10 +102,9 @@ assoc = $(word 1,\
dissoc = $(word 1,\
$(foreach hm,$(call _clone_obj,$(word 1,$(1))),\
$(hm) \
- $(foreach key,$(wordlist 2,$(words $(1)),$(1)),\
- $(call _dissoc!,$(hm),$(call str_decode,$($(key)_value))))))
+ $(call _dissoc_seq!,$(hm),$(wordlist 2,$(words $(1)),$(1)))))
-keys = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$(call _string,$(word 4,$(subst _, ,$(v)))))))
+keys = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$(foreach vval,$(word 4,$(subst _, ,$(v))),$(if $(filter $(__keyword)%,$(vval)),$(call _keyword,$(patsubst $(__keyword)%,%,$(vval))),$(call _string,$(vval)))))))
vals = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$($(v)))))
@@ -127,7 +130,10 @@ cons = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_
concat = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(strip $(foreach lst,$1,$(call __get_obj_values,$(lst)))))))
-nth = $(word $(call gmsl_plus,1,$(call int_decode,$($(word 2,$(1))_value))),$($(word 1,$(1))_value))
+nth = $(strip \
+ $(if $(call int_lt,$($(word 2,$(1))_value),$(call int_encode,$(call _count,$(word 1,$(1))))),\
+ $(word $(call int_add,1,$(call int_decode,$($(word 2,$(1))_value))),$($(word 1,$(1))_value)),\
+ $(call _error,nth: index out of range)))
sfirst = $(word 1,$($(1)_value))
@@ -155,7 +161,7 @@ srest = $(word 1,$(foreach new_list,$(call _list),\
# (function object) using the remaining arguments.
sapply = $(call $(word 1,$(1))_value,\
$(strip \
- $(wordlist 2,$(call gmsl_subtract,$(words $(1)),1),$(1)) \
+ $(wordlist 2,$(call int_sub,$(words $(1)),1),$(1)) \
$($(word $(words $(1)),$(1))_value)))
# Map a function object over a list object
@@ -209,8 +215,10 @@ core_ns = type obj_type \
nil? nil? \
true? true? \
false? false? \
- symbol _symbol \
+ symbol symbol \
symbol? symbol? \
+ keyword keyword \
+ keyword? keyword? \
function? function? \
string? string? \
\
@@ -231,7 +239,7 @@ core_ns = type obj_type \
- number_subtract \
* number_multiply \
/ number_divide \
- time-secs time_secs \
+ time-ms time_ms \
\
list _list \
list? list? \
diff --git a/make/gmsl.mk b/make/gmsl.mk
index e988d2c..adfb953 100644
--- a/make/gmsl.mk
+++ b/make/gmsl.mk
@@ -47,66 +47,13 @@ __mal_gmsl_included := true
#
# ----------------------------------------------------------------------------
-
-# Numbers
-__gmsl_sixteen := x x x x x x x x x x x x x x x x
-__gmsl_input_int := $(foreach a,$(__gmsl_sixteen), \
- $(foreach b,$(__gmsl_sixteen), \
- $(foreach c,$(__gmsl_sixteen), \
- $(__gmsl_sixteen)))))
-
-int_decode = $(words $1)
-int_encode = $(wordlist 1,$1,$(__gmsl_input_int))
-
-__gmsl_int_wrap = $(call int_decode,$(call $1,$(call int_encode,$2),$(call int_encode,$3)))
-
-int_plus = $(strip $1 $2)
-int_subtract = $(strip $(if $(call int_gte,$1,$2), \
- $(filter-out xx,$(join $1,$2)), \
- $(warning Subtraction underflow)))
-int_multiply = $(strip $(foreach a,$1,$2))
-# _error function must be provided to report/catch division by zero
-int_divide = $(strip $(if $2, \
- $(if $(call int_gte,$1,$2), \
- x $(call int_divide,$(call int_subtract,$1,$2),$2),), \
- $(call _error,Division by zero)))
-
-int_max = $(subst xx,x,$(join $1,$2))
-int_min = $(subst xx,x,$(filter xx,$(join $1,$2)))
-int_gt = $(strip $(filter-out $(words $2),$(words $(call int_max,$1,$2))))
-int_gte = $(strip $(call int_gt,$1,$2)$(call int_eq,$1,$2))
-int_lt = $(strip $(filter-out $(words $1),$(words $(call int_max,$1,$2))))
-int_lte = $(strip $(call int_lt,$1,$2)$(call int_eq,$1,$2))
-int_eq = $(strip $(filter $(words $1),$(words $2)))
-int_ne = $(strip $(filter-out $(words $1),$(words $2)))
-
-gmsl_plus = $(call __gmsl_int_wrap,int_plus,$1,$2)
-gmsl_subtract = $(call __gmsl_int_wrap,int_subtract,$1,$2)
-gmsl_multiply = $(call __gmsl_int_wrap,int_multiply,$1,$2)
-gmsl_divide = $(call __gmsl_int_wrap,int_divide,$1,$2)
-
-
# Strings
-__gmsl_characters := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
-__gmsl_characters += a b c d e f g h i j k l m n o p q r s t u v w x y z
-__gmsl_characters += 0 1 2 3 4 5 6 7 8 9
-__gmsl_characters += ` ~ ! @ \# $$ % ^ & * ( ) - _ = +
-__gmsl_characters += { } [ ] \ : ; ' " < > , . / ? |
-__syntax_highlight_protect = #"'`
-
-
-__gmsl_space :=
-__gmsl_space +=
-
-gmsl_strlen = $(strip $(eval __temp := $(subst $(__gmsl_space),x,$1)) \
- $(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,x,$(__temp)))) \
- $(eval __temp := $(subst x,x ,$(__temp))) \
- $(words $(__temp)))
-
-gmsl_merge = $(strip $(if $2, \
- $(if $(call _EQ,1,$(words $2)), \
- $2,$(firstword $2)$1$(call gmsl_merge,$1,$(wordlist 2,$(words $2),$2)))))
+gmsl_characters := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+gmsl_characters += a b c d e f g h i j k l m n o p q r s t u v w x y z
+gmsl_characters += 0 1 2 3 4 5 6 7 8 9
+gmsl_characters += ` ~ ! @ \# $$ % ^ & * ( ) - _ = +
+gmsl_characters += { } [ ] \ : ; ' " < > , . / ? |
gmsl_pairmap = $(strip \
$(if $2$3,$(call $1,$(word 1,$2),$(word 1,$3)) \
diff --git a/make/numbers.mk b/make/numbers.mk
new file mode 100644
index 0000000..b0fa29a
--- /dev/null
+++ b/make/numbers.mk
@@ -0,0 +1,409 @@
+#
+# mal (Make a Lisp) number types
+#
+
+ifndef __mal_numbers_included
+__mal_numbers_included := true
+
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)util.mk
+
+LIST20_X := x x x x x x x x x x x x x x x x x x x x
+LIST100_X := $(foreach x,$(LIST20_X),X X X X X)
+LIST100_0 := $(foreach x,$(LIST20_X),0 0 0 0 0)
+LIST100_9 := $(foreach x,$(LIST20_X),9 9 9 9 9)
+
+###
+### general numeric utility functions
+###
+
+int_encode = $(strip $(call _reverse,\
+ $(eval __temp := $(1))\
+ $(foreach a,0 1 2 3 4 5 6 7 8 9,\
+ $(eval __temp := $$(subst $$a,$$a$$(SPACE),$(__temp))))$(__temp)))
+
+int_decode = $(strip $(call _join,$(call _reverse,$(1))))
+
+# trim extaneous zero digits off the end (front of number)
+_trim_zeros = $(if $(call _EQ,0,$(strip $(1))),0,$(if $(call _EQ,0,$(word 1,$(1))),$(call _trim_zeros,$(wordlist 2,$(words $(1)),$(1))),$(1)))
+trim_zeros = $(strip $(if $(call _EQ,0,$(strip $(1))),$(1),$(call _reverse,$(call _trim_zeros,$(call _reverse,$(1))))))
+
+# drop the last element of a list of words/digits
+drop_last = $(call _reverse,$(wordlist 2,$(words $(1)),$(call _reverse,$(1))))
+
+### utility function tests
+
+#$(info $(filter-out 1,$(filter 1%,1 132 456)))
+#$(info (int_encode 13): [$(call int_encode,13)])
+#$(info (int_encode 156463): [$(call int_encode,156463)])
+#$(info (int_decode (int_encode 156463)): [$(call int_decode,$(call int_encode,156463))])
+
+#$(info trim_zeros(0 0 0): [$(call trim_zeros,0 0 0)])
+
+
+###
+### comparisons
+###
+
+# compare two digits and return 'true' if digit 1 is less than or
+# equal to digit 2
+_lte_digit = $(strip \
+ $(if $(call _EQ,$(1),$(2)),\
+ true,\
+ $(if $(call _EQ,0,$(1)),\
+ true,\
+ $(if $(wordlist $(1),$(2),$(LIST20_X)),\
+ true,\
+ ))))
+
+# compare two lists of digits (MSB->LSB) of equal length and return
+# 'true' if number 1 is less than number 2
+_lte_digits = $(strip \
+ $(if $(strip $(1)),\
+ $(if $(call _EQ,$(word 1,$(1)),$(word 1,$(2))),\
+ $(call _lte_digits,$(wordlist 2,$(words $(1)),$(1)),$(wordlist 2,$(words $(2)),$(2))),\
+ $(if $(call _lte_digit,$(word 1,$(1)),$(word 1,$(2))),true,)),\
+ true))
+
+### lte/less than or equal to
+
+int_lte_encoded = $(strip \
+ $(foreach len1,$(words $(1)),$(foreach len2,$(words $(2)),\
+ $(if $(call _EQ,$(len1),$(len2)),\
+ $(call _lte_digits,$(call _reverse,$(1)),$(call _reverse,$(2))),\
+ $(if $(wordlist $(len1),$(len2),$(LIST100_X)),\
+ true,\
+ )))))
+
+int_lte = $(call int_lte_encoded,$(call int_encode,$(1)),$(call int_encode,$(2)))
+
+### lt/less than
+
+int_lt_encoded = $(strip \
+ $(if $(call _EQ,$(strip $(1)),$(strip $(2))),\
+ ,\
+ $(call int_lte_encoded,$(1),$(2))))
+
+int_lt = $(call int_lt_encoded,$(call int_encode,$(1)),$(call int_encode,$(2)))
+
+### gte/greater than or equal to
+
+int_gte_encoded = $(strip \
+ $(if $(call _EQ,$(strip $(1)),$(strip $(2))),\
+ true,\
+ $(if $(call int_lte_encoded,$(1),$(2)),,true)))
+
+int_gte = $(call int_gte_encoded,$(call int_encode,$(1)),$(call int_encode,$(2)))
+
+### gt/greater than
+
+int_gt_encoded = $(strip \
+ $(if $(call _EQ,$(strip $(1)),$(strip $(2))),\
+ ,\
+ $(call int_gte_encoded,$(1),$(2))))
+
+int_gt = $(call int_gt_encoded,$(call int_encode,$(1)),$(call int_encode,$(2)))
+
+#$(info _lte_digit,7,8: [$(call _lte_digit,7,8)])
+#$(info _lte_digit,8,8: [$(call _lte_digit,8,8)])
+#$(info _lte_digit,2,1: [$(call _lte_digit,2,1)])
+#$(info _lte_digit,0,0: [$(call _lte_digit,0,0)])
+#$(info _lte_digit,0,1: [$(call _lte_digit,0,1)])
+#$(info _lte_digit,1,0: [$(call _lte_digit,1,0)])
+
+#$(info _lte_digits,1 2 3,1 2 4: [$(call _lte_digits,1 2 3,1 2 4)])
+#$(info _lte_digits,1 2 4,1 2 4: [$(call _lte_digits,1 2 4,1 2 4)])
+#$(info _lte_digits,1 2 5,1 2 4: [$(call _lte_digits,1 2 5,1 2 4)])
+#$(info _lte_digits,4 1,9 0: [$(call _lte_digits,4 1,9 0)])
+
+#$(info int_lte_encoded,1,1: [$(call int_lte_encoded,1,1)])
+#$(info int_lte_encoded,1,2: [$(call int_lte_encoded,1,2)])
+#$(info int_lte_encoded,2,1: [$(call int_lte_encoded,2,1)])
+#$(info int_lte_encoded,0,3: [$(call int_lte_encoded,0,3)])
+#$(info int_lte_encoded,3,0: [$(call int_lte_encoded,3,0)])
+#$(info int_lte_encoded,1 4,0 9: [$(call int_lte_encoded,1 4,0 9)])
+#$(info int_lte_encoded,4 3 2 1,4 3 2 1: [$(call int_lte_encoded,4 3 2 1,4 3 2 1)])
+#$(info int_lte_encoded,5 3 2 1,4 3 2 1: [$(call int_lte_encoded,5 3 2 1,4 3 2 1)])
+#$(info int_lte_encoded,4 3 2 1,5 3 2 1: [$(call int_lte_encoded,4 3 2 1,5 3 2 1)])
+
+#$(info int_lte,1,1: [$(call int_lte,1,1)])
+#$(info int_lte,1,2: [$(call int_lte,1,2)])
+#$(info int_lte,2,1: [$(call int_lte,2,1)])
+#$(info int_lte,0,3: [$(call int_lte,0,3)])
+#$(info int_lte,3,0: [$(call int_lte,3,0)])
+#$(info int_lte,1234,1234: [$(call int_lte,1234,1234)])
+#$(info int_lte,1235,1234: [$(call int_lte,1235,1234)])
+#$(info int_lte,1234,1235: [$(call int_lte,1234,1235)])
+#
+#$(info int_lt,1,1: [$(call int_lt,1,1)])
+#$(info int_lt,1,2: [$(call int_lt,1,2)])
+#$(info int_lt,2,1: [$(call int_lt,2,1)])
+#$(info int_lt,0,3: [$(call int_lt,0,3)])
+#$(info int_lt,3,0: [$(call int_lt,3,0)])
+#$(info int_lt,1234,1234: [$(call int_lt,1234,1234)])
+#$(info int_lt,1235,1234: [$(call int_lt,1235,1234)])
+#$(info int_lt,1234,1235: [$(call int_lt,1234,1235)])
+#
+#$(info int_gte,1,1: [$(call int_gte,1,1)])
+#$(info int_gte,1,2: [$(call int_gte,1,2)])
+#$(info int_gte,2,1: [$(call int_gte,2,1)])
+#$(info int_gte,0,3: [$(call int_gte,0,3)])
+#$(info int_gte,3,0: [$(call int_gte,3,0)])
+#$(info int_gte,1234,1234: [$(call int_gte,1234,1234)])
+#$(info int_gte,1235,1234: [$(call int_gte,1235,1234)])
+#$(info int_gte,1234,1235: [$(call int_gte,1234,1235)])
+#
+#$(info int_gt,1,1: [$(call int_gt,1,1)])
+#$(info int_gt,1,2: [$(call int_gt,1,2)])
+#$(info int_gt,2,1: [$(call int_gt,2,1)])
+#$(info int_gt,0,3: [$(call int_gt,0,3)])
+#$(info int_gt,3,0: [$(call int_gt,3,0)])
+#$(info int_gt,1234,1234: [$(call int_gt,1234,1234)])
+#$(info int_gt,1235,1234: [$(call int_gt,1235,1234)])
+#$(info int_gt,1234,1235: [$(call int_gt,1234,1235)])
+
+
+###
+### addition
+###
+
+
+# add_digits_with_carry
+_add_digit = $(words $(if $(strip $(1)),$(wordlist 1,$(1),$(LIST20_X)),) \
+ $(if $(strip $(2)),$(wordlist 1,$(2),$(LIST20_X)),))
+
+# add one to a single digit
+_inc_digit = $(words $(wordlist 1,$(if $(1),$(1),0),$(LIST20_X)) x)
+
+# add two encoded numbers digit by digit without resolving carries
+# (each digit will be larger than 9 if there is a carry value)
+_add = $(if $(1)$(2),$(call _add_digit,$(word 1,$(1)),$(word 1,$(2))) $(call _add,$(wordlist 2,$(words $(1)),$(1)),$(wordlist 2,$(words $(2)),$(2))),)
+
+# take the result of _add and resolve the carry values digit by digit
+_resolve_carries = $(strip \
+ $(if $(1),\
+ $(foreach num,$(word 1,$(1)),\
+ $(if $(filter-out 1,$(filter 1%,$(num))),\
+ $(call _resolve_carries,$(call _inc_digit,$(word 2,$(1))) $(wordlist 3,$(words $(1)),$(1)),$(2) $(patsubst 1%,%,$(num))),\
+ $(call _resolve_carries,$(wordlist 2,$(words $(1)),$(1)),$(2) $(num)))),\
+ $(2)))
+
+# add two encoded numbers, returns encoded number
+int_add_encoded = $(call _resolve_carries,$(call _add,$(1),$(2)))
+
+# add two unencoded numbers, returns unencoded number
+int_add = $(call int_decode,$(call int_add_encoded,$(call int_encode,$(1)),$(call int_encode,$(2))))
+
+### addition tests
+
+#$(info _add_digit(7,6,1): [$(call _add_digit,7,6,1)])
+#$(info _add_digit(7,6,0): [$(call _add_digit,7,6,0)])
+#$(info _add_digit(7,6,0): [$(call _add_digit,7,6,0)])
+#$(info _carries(12 14 15): [$(call _carries,12 14 15)])
+#$(info _inc_digit(0): $(call _inc_digit,0))
+#$(info _inc_digit(1): $(call _inc_digit,1))
+#$(info _inc_digit(9): $(call _inc_digit,9))
+#$(info _inc_digit(18): $(call _inc_digit,18))
+#$(info int_add_encoded(0,0): [$(call int_add_encoded,0,0)])
+
+#$(info int_add(1,2): [$(call int_add,1,2)])
+#$(info int_add(9,9): [$(call int_add,9,9)])
+#$(info int_add(0,9): [$(call int_add,0,9)])
+#$(info int_add(9,0): [$(call int_add,9,0)])
+#$(info int_add(0,0): [$(call int_add,0,0)])
+#$(info int_add(123,456): [$(call int_add,123,456)])
+#$(info int_add(678,789): [$(call int_add,678,789)])
+#$(info int_add(1,12): [$(call int_add,1,12)])
+#$(info int_add(123,5): [$(call int_add,123,5)])
+#$(info int_add(123456,9): [$(call int_add,123456,9)])
+#$(info int_add(999999991,9): [$(call int_add,999999991,9)])
+
+###
+### subtraction
+###
+
+_get_zeros = $(if $(call _EQ,0,$(word 1,$(1))),$(call _get_zeros,$(wordlist 2,$(words $(1)),$(1)),$(2) 0),$(2))
+
+# return a 9's complement of a single digit
+_complement9 = $(strip \
+ $(if $(call _EQ,0,$(1)),9,\
+ $(if $(call _EQ,1,$(1)),8,\
+ $(if $(call _EQ,2,$(1)),7,\
+ $(if $(call _EQ,3,$(1)),6,\
+ $(if $(call _EQ,4,$(1)),5,\
+ $(if $(call _EQ,5,$(1)),4,\
+ $(if $(call _EQ,6,$(1)),3,\
+ $(if $(call _EQ,7,$(1)),2,\
+ $(if $(call _EQ,8,$(1)),1,\
+ $(if $(call _EQ,9,$(1)),0)))))))))))
+
+# return a 10's complement of a single digit
+_complement10 = $(call _inc_digit,$(call _complement9,$(1)))
+
+#
+_complement_rest = $(if $(strip $(1)),\
+ $(strip \
+ $(call _complement10,$(word 1,$(1))) \
+ $(foreach digit,$(wordlist 2,$(words $(1)),$(1)),\
+ $(call _complement9,$(digit)))),)
+
+# return the complement of a number
+_complement = $(strip $(call _get_zeros,$(1)) \
+ $(call _complement_rest,$(wordlist $(call _inc_digit,$(words $(call _get_zeros,$(1)))),$(words $(1)),$(1))))
+
+# subtracted encoded number 2 from encoded number 1 and return and
+# encoded number result
+int_sub_encoded = $(strip \
+ $(if $(call _EQ,0,$(strip $(2))),\
+ $(1),\
+ $(call trim_zeros,\
+ $(call drop_last,\
+ $(call int_add_encoded,\
+ $(1),\
+ $(wordlist 1,$(words $(1)),$(call _complement,$(2)) $(LIST100_9)))))))
+
+# subtract unencoded number 2 from unencoded number 1 and return
+# unencoded result
+int_sub = $(call int_decode,$(call int_sub_encoded,$(call int_encode,$(1)),$(call int_encode,$(2))))
+
+### subtraction tests
+
+#$(info _get_zeros(5 7): [$(call _get_zeros,5 7)])
+#$(info _get_zeros(0 0 0 2): [$(call _get_zeros,0 0 0 2)])
+#$(info _get_zeros(0 0 0 2 5): [$(call _get_zeros,0 0 0 2 5)])
+
+#$(info _complement(0): [$(call _complement,0)])
+#$(info _complement(1): [$(call _complement,1)])
+#$(info _complement(9): [$(call _complement,9)])
+#$(info _complement(5 7): [$(call _complement,5 7)])
+#$(info _complement(0 0 0 2): [$(call _complement,0 0 0 2)])
+#$(info _complement(0 0 0 5 4 3 2 1): [$(call _complement,0 0 0 5 4 3 2 1)])
+
+#$(info int_sub_encoded(0 0 1, 3 1): [$(call int_sub_encoded,0 0 1,3 1)])
+#$(info int_sub_encoded(2, 2): [$(call int_sub_encoded,2,2)])
+
+#$(info int_sub(2,1): [$(call int_sub,2,1)])
+#$(info int_sub(2,0): [$(call int_sub,2,0)])
+#$(info int_sub(2,2): [$(call int_sub,2,2)])
+#$(info int_sub(100,13): [$(call int_sub,100,13)])
+#$(info int_sub(100,99): [$(call int_sub,100,99)])
+#$(info int_sub(91,19): [$(call int_sub,91,19)])
+
+
+###
+### multiplication
+###
+
+# multiply two digits
+#_mult_digit = $(words $(foreach x,$(1),$(2)))
+_mult_digit = $(strip \
+ $(words $(foreach x,$(wordlist 1,$(1),$(LIST20_X)),\
+ $(wordlist 1,$(2),$(LIST20_X)))))
+
+# multipy every digit of number 1 with number 2
+# params: digits, digit, indent_zeros, results
+_mult_row = $(if $(strip $(1)),$(call _mult_row,$(wordlist 2,$(words $(1)),$(1)),$(2),$(3)0,$(4) $(call _mult_digit,$(word 1,$(1)),$(2))$(3)),$(4))
+
+# multiply every digit of number 2 with every digit of number 1 adding
+# correct zero padding to the end of each result
+# params: digits, digits, indent_zeros, results
+_mult_each = $(if $(strip $(2)),$(call _mult_each,$(1),$(wordlist 2,$(words $(2)),$(2)),$(3)0,$(4) $(call _mult_row,$(1),$(word 1,$(2)),$(3))),$(4))
+
+# add up a bunch of unencoded numbers. Basically reduce into the first number
+_add_many = $(if $(word 2,$(1)),$(call _add_many,$(call int_add,$(word 1,$(1)),$(word 2,$(1))) $(wordlist 3,$(words $(1)),$(1))),$(1))
+
+# multiply two encoded numbers, returns encoded number
+int_mult_encoded = $(call trim_zeros,$(call int_encode,$(call _add_many,$(call _mult_each,$(1),$(2)))))
+
+# multiply two unencoded numbers, returns unencoded number
+int_mult = $(call int_decode,$(call int_mult_encoded,$(call int_encode,$(1)),$(call int_encode,$(2))))
+
+#$(info _mult_digit(8,6): [$(call _mult_digit,8,6)])
+#$(info _mult_digit(7,6): [$(call _mult_digit,7,6)])
+#$(info _mult_row(8,6): [$(call _mult_row,8,6)])
+#$(info _mult_row(8 7,6): [$(call _mult_row,8 7,6)])
+#$(info _mult_row(8 7 3,6): [$(call _mult_row,8 7 3,6)])
+#$(info _mult_each(8 7 6, 4 3 2): [$(call _mult_each,8 7 6,4 3 2)])
+#$(info _add_many(123 234 345 456): [$(call _add_many,123 234 345 456)])
+
+#$(info int_mult_encoded(8 7 3,6): [$(call int_mult_encoded,8 7 3,6)])
+#$(info int_mult_encoded(8 7 3,0): [$(call int_mult_encoded,8 7 3,0)])
+
+#$(info int_mult(378,6): [$(call int_mult,378,6)])
+#$(info int_mult(678,234): [$(call int_mult,678,234)])
+#$(info int_mult(1,23456): [$(call int_mult,1,23456)])
+#$(info int_mult(0,23456): [$(call int_mult,0,23456)])
+#$(info int_mult(0,0): [$(call int_mult,0,0)])
+
+###
+### division
+###
+
+# return list of zeros needed to pad number 2 to the same length as number 1
+_zero_pad = $(strip $(wordlist 1,$(call int_sub,$(words $(1)),$(words $(2))),$(LIST100_0)))
+
+# num1, num2, zero pad, result_accumulator
+# algorithm:
+# - B = pad with zeros to make same digit length as A
+# - loop
+# - if (B <= A)
+# - A = subtract B from A
+# - C = C + 10^(B pad.length)
+# - else
+# - if B.length < origin B.length: break
+# - chop least significant digit of B
+_div = $(strip \
+ $(if $(call int_lte_encoded,$(3) $(2),$(1)),\
+ $(call _div,$(call int_sub_encoded,$(1),$(3) $(2)),$(2),$(3),$(call int_add_encoded,$(4),$(3) 1)),\
+ $(if $(3),\
+ $(call _div,$(1),$(2),$(wordlist 2,$(words $(3)),$(3)),$(4)),\
+ $(4))))
+
+# divide two encoded numbers, returns encoded number
+int_div_encoded = $(strip \
+ $(if $(call _EQ,0,$(1)),\
+ 0,\
+ $(if $(call _EQ,$(1),$(2)),\
+ 1,\
+ $(if $(call int_gt_encoded,$(2),$(1)),\
+ 0,\
+ $(call _div,$(1),$(2),$(call _zero_pad,$(1),$(2)),0)))))
+
+# divide two unencoded numbers, returns unencoded number
+int_div = $(call int_decode,$(call int_div_encoded,$(call int_encode,$(1)),$(call int_encode,$(2))))
+
+### division tests
+
+#$(info _zero_pad(1 2 3 4,1 3): [$(call _zero_pad,1 2 3 4,1 3)])
+#$(info _zero_pad(1 2,1 3): [$(call _zero_pad,1 2,1 3)])
+#$(info _zero_pad(2,1 3): [$(call _zero_pad,1 2,1 3)])
+#
+#$(info int_div_encoded(2,1): [$(call int_div_encoded,2,1)])
+#$(info int_div_encoded(3,1): [$(call int_div_encoded,3,1)])
+#$(info int_div_encoded(3,2): [$(call int_div_encoded,3,2)])
+#$(info int_div_encoded(0,7): [$(call int_div_encoded,0,7)])
+#$(info int_div_encoded(0 3,0 2): [$(call int_div_encoded,0 3,0 2)])
+#$(info int_div_encoded(0 3,5): [$(call int_div_encoded,0 3,5)])
+#
+#$(info int_div(5,1): [$(call int_div,5,1)])
+#$(info int_div(5,2): [$(call int_div,5,2)])
+#$(info int_div(123,7): [$(call int_div,123,7)])
+#$(info int_div(100,7): [$(call int_div,100,7)])
+
+
+### combination tests
+
+# (/ (- (+ 515 (* 222 311)) 300) 41)
+#$(info int_mult,222,311: [$(call int_mult,222,311)])
+#$(info int_add(515,69042): [$(call int_add,515,69042)])
+#$(info int_sub(69557,300): [$(call int_sub,69557,300)])
+#$(info int_div(69257,41): [$(call int_div,69257,41)])
+
+###############################################################
+
+all:
+ @true
+
+endif
+
+# vim: ts=2 et
diff --git a/make/printer.mk b/make/printer.mk
index a1f2559..10b9789 100644
--- a/make/printer.mk
+++ b/make/printer.mk
@@ -30,7 +30,9 @@ number_pr_str = $(call int_decode,$($(1)_value))
symbol_pr_str = $($(1)_value)
-string_pr_str = $(if $(2),"$(subst $(DQUOTE),$(ESC_DQUOTE),$(subst $(SLASH),$(SLASH)$(SLASH),$(call str_decode,$($(1)_value))))",$(call str_decode,$($(1)_value)))
+keyword_pr_str = $(COLON)$(patsubst $(__keyword)%,%,$(call str_decode,$($(1)_value)))
+
+string_pr_str = $(if $(filter $(__keyword)%,$(call str_decode,$($(1)_value))),$(COLON)$(patsubst $(__keyword)%,%,$(call str_decode,$($(1)_value))),$(if $(2),"$(subst $(DQUOTE),$(ESC_DQUOTE),$(subst $(SLASH),$(SLASH)$(SLASH),$(call str_decode,$($(1)_value))))",$(call str_decode,$($(1)_value))))
function_pr_str = <$(if $(word 6,$(value $(1)_value)),$(wordlist 1,5,$(value $(1)_value))...,$(value $(1)_value))>
@@ -38,7 +40,7 @@ list_pr_str = ($(foreach v,$(call __get_obj_values,$(1)),$(call _pr_str,$(v),$(2
vector_pr_str = [$(foreach v,$(call __get_obj_values,$(1)),$(call _pr_str,$(v),$(2)))]
-hash_map_pr_str = {$(foreach v,$(call __get_obj_values,$(1)),"$(foreach hcode,$(word 3,$(subst _, ,$(1))),$(patsubst $(1)_%,%,$(v:%_value=%)))" $(call _pr_str,$($(v)),$(2)))}
+hash_map_pr_str = {$(foreach v,$(call __get_obj_values,$(1)),$(foreach vval,$(foreach hcode,$(word 3,$(subst _, ,$(1))),$(patsubst $(1)_%,%,$(v:%_value=%))),$(if $(filter $(__keyword)%,$(vval)),$(patsubst $(__keyword)%,$(COLON)%,$(vval)),"$(vval)")) $(call _pr_str,$($(v)),$(2)))}
atom_pr_str = (atom $(call _pr_str,$($(1)_value),$(2)))
diff --git a/make/reader.mk b/make/reader.mk
index 8d04596..8571785 100755
--- a/make/reader.mk
+++ b/make/reader.mk
@@ -52,6 +52,17 @@ $(foreach ch,$(word 1,$($(1))),\
))
endef
+define READ_KEYWORD
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(ch),\
+ $(if $(filter $(_TOKEN_DELIMS),$(ch)),\
+ ,\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(and $(READER_DEBUG),$(info READ_KEYWORD ch: $(ch) | $($(1))))\
+ $(ch)$(strip $(call READ_KEYWORD,$(1)))),\
+ ))
+endef
+
define READ_ATOM
$(foreach ch,$(word 1,$($(1))),\
$(if $(filter $(NUMBERS),$(ch)),\
@@ -62,6 +73,9 @@ $(foreach ch,$(word 1,$($(1))),\
$(eval $(if $(filter $(DQUOTE),$(word 1,$($(1)))),\
$(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\
$(call _error,Expected '$(DQUOTE)' in; $($(1))))),\
+ $(if $(filter $(COLON),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call _keyword,$(call READ_KEYWORD,$(1))),\
$(foreach sym,$(call READ_SYMBOL,$(1)),\
$(if $(call _EQ,nil,$(sym)),\
$(__nil),\
@@ -69,7 +83,7 @@ $(foreach ch,$(word 1,$($(1))),\
$(__true),\
$(if $(call _EQ,false,$(sym)),\
$(__false),\
- $(call _symbol,$(sym)))))))))
+ $(call _symbol,$(sym))))))))))
endef
# read and return tokens until $(2) found
diff --git a/make/step3_env.mk b/make/step3_env.mk
index b548874..ddcb70f 100644
--- a/make/step3_env.mk
+++ b/make/step3_env.mk
@@ -57,7 +57,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
diff --git a/make/step4_if_fn_do.mk b/make/step4_if_fn_do.mk
index 47f7fe4..22d1d21 100644
--- a/make/step4_if_fn_do.mk
+++ b/make/step4_if_fn_do.mk
@@ -57,7 +57,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
diff --git a/make/step6_file.mk b/make/step6_file.mk
index f0298a9..b05f723 100644
--- a/make/step6_file.mk
+++ b/make/step6_file.mk
@@ -57,7 +57,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
diff --git a/make/step7_quote.mk b/make/step7_quote.mk
index df3745f..2af0248 100644
--- a/make/step7_quote.mk
+++ b/make/step7_quote.mk
@@ -70,7 +70,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
diff --git a/make/step8_macros.mk b/make/step8_macros.mk
index 96e5d31..172e64d 100644
--- a/make/step8_macros.mk
+++ b/make/step8_macros.mk
@@ -82,7 +82,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
diff --git a/make/step9_interop.mk b/make/step9_try.mk
index 984740a..587f400 100644
--- a/make/step9_interop.mk
+++ b/make/step9_try.mk
@@ -82,7 +82,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
@@ -99,10 +100,21 @@ $(if $(__ERROR),,\
$(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
$(if $(call _EQ,macroexpand,$($(a0)_value)),\
$(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\
- $(if $(call _EQ,make*,$($(a0)_value)),\
+ $(if $(call _EQ,try*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
- $(and $(EVAL_DEBUG),$(info make*: $$(eval __result := $(call str_decode,$(value $(a1)_value)))))\
- $(eval __result := $(call str_decode,$(value $(a1)_value)))$(call _string,$(__result))),\
+ $(foreach res,$(call EVAL,$(a1),$(2)),\
+ $(if $(__ERROR),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach a20,$(call _nth,$(a2),0),\
+ $(if $(call _EQ,catch*,$($(a20)_value)),\
+ $(foreach a21,$(call _nth,$(a2),1),\
+ $(foreach a22,$(call _nth,$(a2),2),\
+ $(foreach binds,$(call _list,$(a21)),\
+ $(foreach catch_env,$(call ENV,$(2),$(binds),$(__ERROR)),\
+ $(eval __ERROR :=)\
+ $(call EVAL,$(a22),$(catch_env)))))),\
+ $(res)))),\
+ $(res)))),\
$(if $(call _EQ,do,$($(a0)_value)),\
$(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
$(if $(call _EQ,if,$($(a0)_value)),\
diff --git a/make/stepA_more.mk b/make/stepA_mal.mk
index 050366c..80909c7 100644
--- a/make/stepA_more.mk
+++ b/make/stepA_mal.mk
@@ -82,7 +82,8 @@ $(if $(__ERROR),,\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
$(foreach res,$(call EVAL,$(a2),$(2)),\
- $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(__ERROR),,\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),))))),\
$(if $(call _EQ,let*,$($(a0)_value)),\
$(foreach a1,$(call _nth,$(1),1),\
$(foreach a2,$(call _nth,$(1),2),\
diff --git a/make/tests/step9_interop.mal b/make/tests/stepA_mal.mal
index 9b1a2f9..768a929 100644
--- a/make/tests/step9_interop.mal
+++ b/make/tests/stepA_mal.mal
@@ -14,6 +14,6 @@
(make* "$(foreach v,a b c,X$(v)Y)")
;=>"XaY XbY XcY"
-(read-string (make* "($(foreach v,1 2 3,$(call gmsl_plus,1,$(v))))"))
+(read-string (make* "($(foreach v,1 2 3,$(call int_add,1,$(v))))"))
;=>(2 3 4)
diff --git a/make/types.mk b/make/types.mk
index b971fb1..6320396 100644
--- a/make/types.mk
+++ b/make/types.mk
@@ -6,7 +6,9 @@ ifndef __mal_types_included
__mal_types_included := true
_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)gmsl.mk
include $(_TOP_DIR)util.mk
+include $(_TOP_DIR)numbers.mk
# Low-level type implemenation
@@ -15,9 +17,10 @@ include $(_TOP_DIR)util.mk
__obj_magic = ⍄⁊
# \u2256
__equal = ≛
+__keyword = ʞ
__obj_hash_code = 0
-__new_obj_hash_code = $(eval __obj_hash_code := $(call gmsl_plus,1,$(__obj_hash_code)))$(__obj_hash_code)
+__new_obj_hash_code = $(eval __obj_hash_code := $(call int_add,1,$(__obj_hash_code)))$(__obj_hash_code)
__new_obj = $(__obj_magic)_$(1)_$(call __new_obj_hash_code)
@@ -36,12 +39,12 @@ __var_print = $(foreach v,$(1),\
$(foreach var,$(call __var_name,$(v)),\
$(if $(or $(call _list?,$(v)),$(call _vector?,$(v))),\
$(info $(2)$(var):)\
- $(eval __var_idx := $(call gmsl_plus,1,$(__var_idx)))\
+ $(eval __var_idx := $(call int_add,1,$(__var_idx)))\
$(foreach lidx,__lidx_$(__var_idx),\
$(eval $(lidx) := 0)\
$(foreach val,$($(v)_value),\
$(call __var_print,$(val),$(2)$(SPACE)$(SPACE)$($(lidx)): )\
- $(eval $(lidx) := $(call gmsl_plus,1,$($(lidx)))))),\
+ $(eval $(lidx) := $(call int_add,1,$($(lidx)))))),\
$(if $(call _hash_map?,$(v)),\
$(info $(2)$(var):)\
$(foreach vkey,$(filter $(v)_%,$(.VARIABLES)),\
@@ -50,6 +53,8 @@ __var_print = $(foreach v,$(1),\
$(call __var_print,$($(vkey)),$(2)$(SPACE)$(SPACE)$(SPACE)$(SPACE)))),\
$(if $(call _symbol?,$(v)),\
$(info $(2)$(var): $($(v)_value)),\
+ $(if $(call _keyword?,$(v)),\
+ $(info $(2)$(var): $($(v)_value)),\
$(if $(call _number?,$(v)),\
$(info $(2)$(var): $(call int_decode,$($(v)_value))),\
$(if $(call _nil?,$(v)),\
@@ -58,7 +63,7 @@ __var_print = $(foreach v,$(1),\
$(if $(word 6,$(value $(v)_value)),\
$(info $(2)$(var): $(wordlist 1,5,$(value $(v)_value))...),\
$(info $(2)$(var): $(value $(v)_value))),\
- $(info $(2)$(var): ...)))))))))
+ $(info $(2)$(var): ...))))))))))
_visualize_memory = $(foreach var,$(sort $(foreach vv,$(filter $(__obj_magic)_%,$(.VARIABLES)),$(call __var_name,$(vv)))),$(call __var_print,$(__obj_magic)_$(var)))
@@ -83,7 +88,8 @@ _obj_type = $(strip \
$(if $(filter $(__obj_magic)_list_%,$(1)),list,\
$(if $(filter $(__obj_magic)_numb_%,$(1)),number,\
$(if $(filter $(__obj_magic)_func_%,$(1)),function,\
- $(if $(filter $(__obj_magic)_strn_%,$(1)),string,\
+ $(if $(filter $(__obj_magic)_strn_%,$(1)),\
+ $(if $(filter $(__keyword)%,$($(1)_value)),keyword,string),\
$(if $(filter $(__obj_magic)__nil_%,$(1)),nil,\
$(if $(filter $(__obj_magic)_true_%,$(1)),true,\
$(if $(filter $(__obj_magic)_fals_%,$(1)),false,\
@@ -109,7 +115,7 @@ _equal? = $(strip \
$(foreach ot1,$(call _obj_type,$(1)),$(foreach ot2,$(call _obj_type,$(2)),\
$(if $(or $(call _EQ,$(ot1),$(ot2)),\
$(and $(call _sequential?,$(1)),$(call _sequential?,$(2)))),\
- $(if $(or $(call _string?,$(1)),$(call _symbol?,$(1)),$(call _number?,$(1))),\
+ $(if $(or $(call _string?,$(1)),$(call _symbol?,$(1)),$(call _keyword?,$(1)),$(call _number?,$(1))),\
$(call _EQ,$($(1)_value),$($(2)_value)),\
$(if $(or $(call _vector?,$(1)),$(call _list?,$(1)),$(call _hash_map?,$(1))),\
$(if $(and $(call _EQ,$(call _count,$(1)),$(call _count,$(2))),\
@@ -130,6 +136,11 @@ _symbol = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_symb_$(hcod
_symbol? = $(if $(filter $(__obj_magic)_symb_%,$(1)),$(__true),)
+# Keywords
+_keyword = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_strn_$(hcode)$(eval $(__obj_magic)_strn_$(hcode)_value := $(__keyword)$(1)))
+_keyword? = $(if $(filter $(__obj_magic)_strn_%,$(1)),$(if $(filter $(__keyword)%,$($(1)_value)),$(__true),))
+
+
# Numbers
_pnumber = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_numb_$(hcode)$(eval $(__obj_magic)_numb_$(hcode)_value := $(1)))
_number = $(call _pnumber,$(call int_encode,$(1)))
@@ -176,11 +187,14 @@ _hash_map? = $(if $(filter $(__obj_magic)_hmap_%,$(1)),$(__true),)
# Set multiple key/values in a map
_assoc_seq! = $(call _assoc!,$(1),$(call str_decode,$($(word 1,$(2))_value)),$(word 2,$(2)))$(if $(word 3,$(2)),$(call _assoc_seq!,$(1),$(wordlist 3,$(words $(2)),$(2))),)
+_dissoc_seq! = $(foreach key,$(2),\
+ $(call _dissoc!,$(1),$(call str_decode,$($(key)_value))))
+
# set a key/value in the hash map
-_assoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),$(eval $(1)_size := $(call gmsl_plus,$($(1)_size),1)),)$(eval $(1)_$(k)_value := $(3))$(1))
+_assoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),$(eval $(1)_size := $(call int_add,$($(1)_size),1)),)$(eval $(1)_$(k)_value := $(3))$(1))
# unset a key in the hash map
-_dissoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(eval $(1)_$(k)_value := $(__undefined))$(eval $(1)_size := $(call gmsl_subtract,$($(1)_size),1))))$(1)
+_dissoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(eval $(1)_$(k)_value := $(__undefined))$(eval $(1)_size := $(call int_sub,$($(1)_size),1))))$(1)
# Hash map and vector functions
@@ -190,14 +204,14 @@ _get = $(strip \
$(if $(call _hash_map?,$(1)),\
$(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$($(1)_$(k)_value))),\
$(if $(call _vector?,$(1)),\
- $(word $(call gmsl_plus,1,$(2)),$($(1)_value)),\
+ $(word $(call int_add,1,$(2)),$($(1)_value)),\
,)))
_contains? = $(strip \
$(if $(call _hash_map?,$(1)),\
$(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(__true))),\
$(if $(call _vector?,$(1)),\
- $(if $(word $(call gmsl_plus,1,$(2)),$($(1)_value)),$(__true),),\
+ $(if $(word $(call int_add,1,$(2)),$($(1)_value)),$(__true),),\
,)))
@@ -205,7 +219,7 @@ _contains? = $(strip \
_sequential? = $(if $(filter $(__obj_magic)_list_% $(__obj_magic)_vect_%,$(1)),$(__true),)
-_nth = $(word $(call gmsl_plus,1,$(2)),$($(1)_value))
+_nth = $(word $(call int_add,1,$(2)),$($(1)_value))
# conj that mutates a sequence in-place to append the call arguments
_conj! = $(eval $(1)_value := $(strip $($(1)_value) $2 $3 $4 $5 $6 $7 $8 $9 $(10) $(11) $(12) $(13) $(14) $(15) $(16) $(17) $(18) $(19) $(20)))$(1)
diff --git a/make/util.mk b/make/util.mk
index 43923fc..ffe635d 100644
--- a/make/util.mk
+++ b/make/util.mk
@@ -10,6 +10,7 @@ include $(_TOP_DIR)gmsl.mk
SEMI := ;
COMMA := ,
+COLON := :
LCURLY := {
RCURLY := }
LPAREN := (
@@ -61,10 +62,34 @@ _EQ = $(if $(subst x$1,,x$2)$(subst x$2,,x$1),,true)
_NOT = $(if $1,,true)
-# READ: read and parse input
-str_encode = $(strip $(eval __temp := $$(subst $$$$,$(_DOL) ,$$(subst $(SPLICE_UNQUOTE),$(_SUQ) ,$$(subst $$(LPAREN),$$(_LP) ,$$(subst $$(RPAREN),$$(_RP) ,$$(subst $$(LCURLY),$$(_LC) ,$$(subst $$(RCURLY),$$(_RC) ,$$(subst $$(NEWLINE),$$(_NL) ,$$(subst $$(SPACE),$(_SP) ,$$1)))))))))$(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,$$a$$(SPACE),$(__temp))))$(__temp))
+# take a list of words and join them with a separator
+# params: words, seperator, result
+_join = $(strip \
+ $(if $(strip $(1)),\
+ $(if $(strip $(3)),\
+ $(call _join,$(wordlist 2,$(words $(1)),$(1)),$(2),$(3)$(2)$(word 1,$(1))),\
+ $(call _join,$(wordlist 2,$(words $(1)),$(1)),$(2),$(word 1,$(1)))),\
+ $(3)))
-str_decode = $(subst $(_SP),$(SPACE),$(subst $(_NL),$(NEWLINE),$(subst $(_LC),$(LCURLY),$(subst $(_RC),$(RCURLY),$(subst $(_LP),$(LPAREN),$(subst $(_RP),$(RPAREN),$(subst $(_SUQ),$(SPLICE_UNQUOTE),$(subst $(_DOL),$$,$(strip $(call gmsl_merge,,$(1)))))))))))
+#$(info _join(1 2 3 4): [$(call _join,1 2 3 4)])
+#$(info _join(1 2 3 4,X): [$(call _join,1 2 3 4, )])
+#$(info _join(1): [$(call _join,1)])
+#$(info _join(): [$(call _join,)])
+
+# reverse list of words
+_reverse = $(if $(1),$(call _reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1))
+
+#$(info reverse(1 2 3 4 5): $(call reverse,1 2 3 4 5))
+
+# str_encode: take a string and return an encoded version of it with
+# every character separated by a space and special characters replaced
+# with special Unicode characters
+str_encode = $(strip $(eval __temp := $$(subst $$$$,$(_DOL) ,$$(subst $(SPLICE_UNQUOTE),$(_SUQ) ,$$(subst $$(LPAREN),$$(_LP) ,$$(subst $$(RPAREN),$$(_RP) ,$$(subst $$(LCURLY),$$(_LC) ,$$(subst $$(RCURLY),$$(_RC) ,$$(subst $$(NEWLINE),$$(_NL) ,$$(subst $$(SPACE),$(_SP) ,$$1)))))))))$(foreach a,$(gmsl_characters),$(eval __temp := $$(subst $$a,$$a$$(SPACE),$(__temp))))$(__temp))
+
+# str_decode: take an encoded string an return an unencoded version of
+# it by replacing the special Unicode charactes with the real
+# characters and with all characters joined into a regular string
+str_decode = $(subst $(_SP),$(SPACE),$(subst $(_NL),$(NEWLINE),$(subst $(_LC),$(LCURLY),$(subst $(_RC),$(RCURLY),$(subst $(_LP),$(LPAREN),$(subst $(_RP),$(RPAREN),$(subst $(_SUQ),$(SPLICE_UNQUOTE),$(subst $(_DOL),$$,$(strip $(call _join,$(1)))))))))))
# Read a whole file substituting newlines with $(_NL)
_read_file = $(subst $(_NL),$(NEWLINE),$(shell out=""; while read -r l; do out="$${out}$${l}$(_NL)"; done < $(1); echo "$$out"))
diff --git a/mal/Makefile b/mal/Makefile
index 7e45ad4..0848621 100644
--- a/mal/Makefile
+++ b/mal/Makefile
@@ -2,7 +2,7 @@
TESTS =
SOURCES_BASE =
-SOURCES_LISP = env.mal core.mal stepA_more.mal
+SOURCES_LISP = env.mal core.mal stepA_mal.mal
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
#.PHONY: stats tests $(TESTS)
diff --git a/mal/core.mal b/mal/core.mal
index 33b6a5e..a6b6bb9 100644
--- a/mal/core.mal
+++ b/mal/core.mal
@@ -4,7 +4,10 @@
["nil?" nil?]
["true?" true?]
["false?" false?]
+ ["symbol" symbol]
["symbol?" symbol?]
+ ["keyword" keyword]
+ ["keyword?" keyword?]
["pr-str" pr-str]
["str" str]
diff --git a/mal/step9_try.mal b/mal/step9_try.mal
new file mode 100644
index 0000000..ac54cef
--- /dev/null
+++ b/mal/step9_try.mal
@@ -0,0 +1,178 @@
+(load-file "../mal/env.mal")
+(load-file "../mal/core.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! is-pair (fn* [x]
+ (if (sequential? x)
+ (if (> (count x) 0)
+ true))))
+
+(def! QUASIQUOTE (fn* [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (nth ast 1)
+
+ (if (is-pair (first ast))
+ (if (= 'splice-unquote (first (first ast)))
+ true))
+ (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast)))
+
+ "else"
+ (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast))))))
+
+(def! is-macro-call (fn* [ast env]
+ (if (list? ast)
+ (let* [a0 (first ast)]
+ (if (symbol? a0)
+ (if (env-find env a0)
+ (let* [m (meta (env-get env a0))]
+ (if m
+ (if (get m "ismacro")
+ true)))))))))
+
+(def! MACROEXPAND (fn* [ast env]
+ (if (is-macro-call ast env)
+ (let* [mac (env-get env (first ast))]
+ (MACROEXPAND (apply mac (rest ast)) env))
+ ast)))
+
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [ast (MACROEXPAND ast env)]
+ (if (not (list? ast))
+ ast
+
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ (= 'quote a0)
+ (nth ast 1)
+
+ (= 'quasiquote a0)
+ (let* [a1 (nth ast 1)]
+ (EVAL (QUASIQUOTE a1) env))
+
+ (= 'defmacro! a0)
+ (let* [a1 (nth ast 1)
+ a2 (nth ast 2)
+ f (EVAL a2 env)
+ m (or (meta f) {})
+ mac (with-meta f (assoc m "ismacro" true))]
+ (env-set env a1 mac))
+
+ (= 'macroexpand a0)
+ (let* [a1 (nth ast 1)]
+ (MACROEXPAND a1 env))
+
+ (= 'try* a0)
+ (if (= 'catch* (nth (nth ast 2) 0))
+ (try*
+ (EVAL (nth ast 1) env)
+ (catch* exc
+ (EVAL (nth (nth ast 2) 2)
+ (new-env env
+ [(nth (nth ast 2)1)]
+ [exc]))))
+ (EVAL (nth ast 1) env))
+
+ (= 'do a0)
+ (let* [el (eval-ast (rest ast) env)]
+ (nth el (- (count el) 1)))
+
+ (= 'if a0)
+ (let* [cond (EVAL (nth ast 1) env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 3)
+ (EVAL (nth ast 3) env)
+ nil)
+ (EVAL (nth ast 2) env)))
+
+ (= 'fn* a0)
+ (fn* [& args]
+ (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng) repl-env))))
+
+;; core.mal: defined directly using mal
+(map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns)
+(env-set repl-env 'eval (fn* [ast] (EVAL ast repl-env)))
+(env-set repl-env '*ARGV* (rest *ARGV*))
+
+;; core.mal: defined using the new 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))))))))")
+
+;; 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]
+ (if (> (count args) 0)
+ (rep (str "(load-file \"" (first args) "\")"))
+ (repl-loop))))
+(apply -main *ARGV*)
diff --git a/mal/stepA_more.mal b/mal/stepA_mal.mal
index 3e2a258..3e2a258 100644
--- a/mal/stepA_more.mal
+++ b/mal/stepA_mal.mal
diff --git a/matlab/+types/Atom.m b/matlab/+types/Atom.m
new file mode 100644
index 0000000..7338ce2
--- /dev/null
+++ b/matlab/+types/Atom.m
@@ -0,0 +1,10 @@
+classdef Atom < handle
+ properties
+ val
+ end
+ methods
+ function atm = Atom(val)
+ atm.val = val;
+ end
+ end
+end
diff --git a/matlab/+types/Function.m b/matlab/+types/Function.m
new file mode 100644
index 0000000..9270ed1
--- /dev/null
+++ b/matlab/+types/Function.m
@@ -0,0 +1,24 @@
+classdef Function < handle
+ properties
+ fn
+ ast
+ env
+ params
+ is_macro = false
+ meta = types.nil;
+ end
+ methods
+ function f = Function(fn, ast, env, params)
+ f.fn = fn;
+ f.ast = ast;
+ f.env = env;
+ f.params = params;
+ end
+
+ function ret = clone(obj)
+ ret = types.Function(obj.fn, obj.ast, obj.env, obj.params);
+ ret.is_macro = obj.is_macro;
+ ret.meta = obj.meta;
+ end
+ end
+end
diff --git a/matlab/+types/HashMap.m b/matlab/+types/HashMap.m
new file mode 100644
index 0000000..f48ba81
--- /dev/null
+++ b/matlab/+types/HashMap.m
@@ -0,0 +1,47 @@
+classdef HashMap < handle
+ properties
+ data = containers.Map();
+ meta = types.nil;
+ end
+ methods
+ function obj = HashMap(varargin)
+ if nargin == 0
+ obj.data = containers.Map();
+ else
+ obj.data = containers.Map(varargin(1:2:end), ...
+ varargin(2:2:end));
+ end
+ end
+
+ function len = length(obj)
+ len = length(obj.data);
+ end
+
+ function ret = get(obj, key)
+ ret = obj.data(key);
+ end
+
+ function ret = set(obj, key, val)
+ obj.data(key) = val;
+ ret = val;
+ end
+
+ function ret = keys(obj)
+ ret = obj.data.keys();
+ end
+
+ function ret = values(obj)
+ ret = obj.data.values();
+ end
+
+ function ret = clone(obj)
+ ret = types.HashMap();
+ if length(obj) > 0
+ ret.data = containers.Map(obj.data.keys(), obj.data.values());
+ else
+ ret.data = containers.Map();
+ end
+ ret.meta = obj.meta;
+ end
+ end
+end
diff --git a/matlab/+types/List.m b/matlab/+types/List.m
new file mode 100644
index 0000000..1a9571c
--- /dev/null
+++ b/matlab/+types/List.m
@@ -0,0 +1,66 @@
+classdef List < handle
+ properties
+ data = {}
+ meta = types.nil;
+ end
+ methods
+ function obj = List(varargin)
+ obj.data = varargin;
+ end
+
+ function len = length(obj)
+ len = length(obj.data);
+ end
+
+ function ret = get(obj, idx)
+ ret = obj.data{idx};
+ end
+
+ function ret = set(obj, key, val)
+ obj.data{key} = val;
+ ret = val;
+ end
+
+ function ret = append(obj, val)
+ obj.data{end+1} = val;
+ ret = val;
+ end
+
+ function ret = slice(obj, start, last)
+ if nargin < 3
+ last = length(obj.data);
+ end
+ ret = types.List(obj.data{start:last});
+ end
+
+ function ret = clone(obj)
+ ret = types.List();
+ ret.data = obj.data;
+ ret.meta = obj.meta;
+ end
+
+% function varargout = subsref(vec, S)
+% % This doesn't work for ranges
+% [varargout{1:nargout}] = builtin('subsref', vec.data, S);
+%
+% varargout = cell(1,max(1,nargout));
+% [varargout{:}] = builtin('subsref',vec.data,S);
+%
+%% switch S.type
+%% case '()'
+%% varargout = cell(1,numel(vec));
+%% varargout{1} = builtin('subsref', vec.data, S);
+%% case '{}'
+%% varargout = cell(1,numel(vec));
+%% varargout{1} = builtin('subsref', vec.data, S);
+%% case '.'
+%% error('Vector property access not yet implemented');
+%% end
+% end
+
+% %function n = numel(varargin)
+% % n = 1;
+% %end
+
+ end
+end
diff --git a/matlab/+types/MalException.m b/matlab/+types/MalException.m
new file mode 100644
index 0000000..1269a1d
--- /dev/null
+++ b/matlab/+types/MalException.m
@@ -0,0 +1,11 @@
+classdef MalException < MException
+ properties
+ obj
+ end
+ methods
+ function exc = MalException(obj)
+ exc@MException('MalException:object', 'MalException');
+ exc.obj = obj;
+ end
+ end
+end
diff --git a/matlab/+types/Nil.m b/matlab/+types/Nil.m
new file mode 100644
index 0000000..7aa8130
--- /dev/null
+++ b/matlab/+types/Nil.m
@@ -0,0 +1,10 @@
+classdef Nil
+ methods
+ function len = length(obj)
+ len = 0;
+ end
+ function ret = eq(a,b)
+ ret = strcmp(class(b),'types.Nil');
+ end
+ end
+end
diff --git a/matlab/+types/Reader.m b/matlab/+types/Reader.m
new file mode 100644
index 0000000..c18ea54
--- /dev/null
+++ b/matlab/+types/Reader.m
@@ -0,0 +1,27 @@
+classdef Reader < handle
+ properties
+ tokens
+ position
+ end
+ methods
+ function rdr = Reader(tokens)
+ rdr.tokens = tokens;
+ rdr.position = 1;
+ end
+ function tok = next(rdr)
+ rdr.position = rdr.position + 1;
+ if rdr.position-1 > length(rdr.tokens)
+ tok = false;
+ else
+ tok = rdr.tokens{rdr.position-1};
+ end
+ end
+ function tok = peek(rdr)
+ if rdr.position > length(rdr.tokens)
+ tok = false;
+ else
+ tok = rdr.tokens{rdr.position};
+ end
+ end
+ end
+end
diff --git a/matlab/+types/Symbol.m b/matlab/+types/Symbol.m
new file mode 100644
index 0000000..5da6b7a
--- /dev/null
+++ b/matlab/+types/Symbol.m
@@ -0,0 +1,13 @@
+classdef Symbol
+ properties
+ name
+ end
+ methods
+ function sym = Symbol(name)
+ sym.name = name;
+ end
+ function ret = eq(a,b)
+ ret = strcmp(a.name, b.name);
+ end
+ end
+end
diff --git a/matlab/+types/Vector.m b/matlab/+types/Vector.m
new file mode 100644
index 0000000..f5dea9c
--- /dev/null
+++ b/matlab/+types/Vector.m
@@ -0,0 +1,20 @@
+classdef Vector < types.List
+ methods
+ function obj = Vector(varargin)
+ obj.data = varargin;
+ end
+
+ function ret = slice(obj, start, last)
+ if nargin < 3
+ last = length(obj.data);
+ end
+ ret = types.Vector(obj.data{2:end});
+ end
+
+ function ret = clone(obj)
+ ret = types.Vector();
+ ret.data = obj.data;
+ ret.meta = obj.meta;
+ end
+ end
+end
diff --git a/matlab/Env.m b/matlab/Env.m
new file mode 100644
index 0000000..66862ff
--- /dev/null
+++ b/matlab/Env.m
@@ -0,0 +1,50 @@
+classdef Env < handle
+ properties
+ data
+ outer
+ end
+ methods
+ function env = Env(outer, binds, exprs)
+ env.data = containers.Map();
+ env.outer = outer;
+
+ if nargin > 1
+ env = Env(outer);
+ for i=1:length(binds)
+ k = binds.get(i).name;
+ if strcmp(k, '&')
+ env.data(binds.get(i+1).name) = exprs.slice(i);
+ break;
+ else
+ env.data(k) = exprs.get(i);
+ end
+ end
+ end
+ end
+
+ function ret = set(env, k, v)
+ env.data(k.name) = v;
+ ret = v;
+ end
+ function ret = find(env, k)
+ if env.data.isKey(k.name)
+ ret = env;
+ else
+ if ~islogical(env.outer)
+ ret = env.outer.find(k);
+ else
+ ret = false;
+ end
+ end
+ end
+ function ret = get(env, k)
+ fenv = env.find(k);
+ if ~islogical(fenv)
+ ret = fenv.data(k.name);
+ else
+ throw(MException('ENV:notfound', ...
+ sprintf('''%s'' not found', k.name)));
+ end
+ end
+ end
+end
diff --git a/matlab/Makefile b/matlab/Makefile
new file mode 100644
index 0000000..eba8fa4
--- /dev/null
+++ b/matlab/Makefile
@@ -0,0 +1,15 @@
+SOURCES_BASE = types.m types/Nil.m types/MalException.m \
+ types/Symbol.m types/List.m types/Vector.m \
+ types/HashMap.m types/Function.m types/Atom.m \
+ types/Reader.m reader.m printer.m
+SOURCES_LISP = Env.m core.m stepA_mal.m
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/matlab/core.m b/matlab/core.m
new file mode 100644
index 0000000..2f9e1f3
--- /dev/null
+++ b/matlab/core.m
@@ -0,0 +1,221 @@
+classdef core
+ methods(Static)
+ function ret = throw(obj)
+ ret = types.nil;
+ throw(types.MalException(obj));
+ end
+
+ function str = pr_str(varargin)
+ strs = cellfun(@(s) printer.pr_str(s,true), varargin, ...
+ 'UniformOutput', false);
+ str = strjoin(strs, ' ');
+ end
+ function str = do_str(varargin)
+ strs = cellfun(@(s) printer.pr_str(s,false), varargin, ...
+ 'UniformOutput', false);
+ str = strjoin(strs, '');
+ end
+ function ret = prn(varargin)
+ strs = cellfun(@(s) printer.pr_str(s,true), varargin, ...
+ 'UniformOutput', false);
+ fprintf('%s\n', strjoin(strs, ' '));
+ ret = types.nil;
+ end
+ function ret = println(varargin)
+ strs = cellfun(@(s) printer.pr_str(s,false), varargin, ...
+ 'UniformOutput', false);
+ fprintf('%s\n', strjoin(strs, ' '));
+ ret = types.nil;
+ end
+
+ function ret = time_ms()
+ secs = now-repmat(datenum('1970-1-1 00:00:00'),size(now));
+ ret = floor(secs.*repmat(24*3600.0*1000,size(now)));
+ end
+
+ function new_hm = assoc(hm, varargin)
+ new_hm = clone(hm);
+ for i=1:2:length(varargin)
+ new_hm.set(varargin{i}, varargin{i+1});
+ end
+ end
+
+ function new_hm = dissoc(hm, varargin)
+ new_hm = clone(hm);
+ ks = intersect(hm.keys(),varargin);
+ remove(new_hm.data, ks);
+ end
+
+ function ret = get(hm, key)
+ if hm == types.nil
+ ret = types.nil;
+ else
+ if hm.data.isKey(key)
+ ret = hm.data(key);
+ else
+ ret = types.nil;
+ end
+ end
+ end
+
+ function ret = keys(hm)
+ ks = hm.keys();
+ ret = types.List(ks{:});
+ end
+
+ function ret = vals(hm)
+ vs = hm.values();
+ ret = types.List(vs{:});
+ end
+
+ function ret = cons(a, seq)
+ cella = [{a}, seq.data];
+ ret = types.List(cella{:});
+ end
+
+ function ret = concat(varargin)
+ if nargin == 0
+ cella = {};
+ else
+ cells = cellfun(@(x) x.data, varargin, ...
+ 'UniformOutput', false);
+ cella = cat(2,cells{:});
+ end
+ ret = types.List(cella{:});
+ end
+
+ function ret = first(seq)
+ if length(seq) < 1
+ ret = types.nil;
+ else
+ ret = seq.get(1);
+ end
+ end
+
+ function ret = rest(seq)
+ cella = seq.data(2:end);
+ ret = types.List(cella{:});
+ end
+
+ function ret = nth(seq, idx)
+ if idx+1 > length(seq)
+ throw(MException('Range:nth', ...
+ 'nth: index out of range'))
+ end
+ ret = seq.get(idx+1);
+ end
+
+ function ret = apply(varargin)
+ f = varargin{1};
+ if isa(f, 'types.Function')
+ f = f.fn;
+ end
+ first_args = varargin(2:end-1);
+ rest_args = varargin{end}.data;
+ args = [first_args rest_args];
+ ret = f(args{:});
+ end
+
+ function ret = map(f, lst)
+ if isa(f, 'types.Function')
+ f = f.fn;
+ end
+ cells = cellfun(@(x) f(x), lst.data, 'UniformOutput', false);
+ ret = types.List(cells{:});
+ end
+
+ function new_obj = with_meta(obj, meta)
+ new_obj = clone(obj);
+ new_obj.meta = meta;
+ end
+
+ function meta = meta(obj)
+ switch class(obj)
+ case {'types.List', 'types.Vector',
+ 'types.HashMap', 'types.Function'}
+ meta = obj.meta;
+ otherwise
+ meta = types.nil;
+ end
+ end
+
+ function ret = reset_BANG(atm, val)
+ atm.val = val;
+ ret = val;
+ end
+
+ function ret = swap_BANG(atm, f, varargin)
+ args = [{atm.val} varargin];
+ if isa(f, 'types.Function')
+ f = f.fn;
+ end
+ atm.val = f(args{:});
+ ret = atm.val;
+ end
+
+ function n = ns()
+ n = containers.Map();
+ n('=') = @types.equal;
+ n('throw') = @core.throw;
+ n('nil?') = @(a) isa(a, 'types.Nil');
+ n('true?') = @(a) isa(a, 'logical') && a == true;
+ n('false?') = @(a) isa(a, 'logical') && a == false;
+ n('symbol') = @(a) types.Symbol(a);
+ n('symbol?') = @(a) isa(a, 'types.Symbol');
+ n('keyword') = @types.keyword;
+ n('keyword?') = @types.keyword_Q;
+
+ n('pr-str') = @core.pr_str;
+ n('str') = @core.do_str;
+ n('prn') = @core.prn;
+ n('println') = @core.println;
+ n('read-string') = @reader.read_str;
+ n('readline') = @(p) input(p, 's');
+ n('slurp') = @fileread;
+
+ n('<') = @(a,b) a<b;
+ n('<=') = @(a,b) a<=b;
+ n('>') = @(a,b) a>b;
+ n('>=') = @(a,b) a>=b;
+ n('+') = @(a,b) a+b;
+ n('-') = @(a,b) a-b;
+ n('*') = @(a,b) a*b;
+ n('/') = @(a,b) floor(a/b);
+ n('time-ms') = @core.time_ms;
+
+ n('list') = @(varargin) types.List(varargin{:});
+ n('list?') = @types.list_Q;
+ n('vector') = @(varargin) types.Vector(varargin{:});
+ n('vector?') = @types.vector_Q;
+ n('hash-map') = @(varargin) types.HashMap(varargin{:});
+ n('map?') = @types.hash_map_Q;
+ n('assoc') = @core.assoc;
+ n('dissoc') = @core.dissoc;
+ n('get') = @core.get;
+ n('contains?') = @(a,b) a.data.isKey(b);
+ n('keys') = @core.keys;
+ n('vals') = @core.vals;
+
+ n('sequential?') = @types.sequential_Q;
+ n('cons') = @core.cons;
+ n('concat') = @core.concat;
+ n('nth') = @core.nth;
+ n('first') = @core.first;
+ n('rest') = @core.rest;
+ n('empty?') = @(a) length(a) == 0;
+ n('count') = @(a) length(a);
+ n('apply') = @core.apply;
+ n('map') = @core.map;
+ n('conj') = @(x) disp('not implemented yet');
+
+ n('with-meta') = @core.with_meta;
+ n('meta') = @core.meta;
+ n('atom') = @types.Atom;
+ n('atom?') = @(a) isa(a, 'types.Atom');
+ n('deref') = @(a) a.val;
+ n('reset!') = @core.reset_BANG;
+ n('swap!') = @core.swap_BANG;
+ end
+ end
+end
+
diff --git a/matlab/printer.m b/matlab/printer.m
new file mode 100644
index 0000000..5200a6e
--- /dev/null
+++ b/matlab/printer.m
@@ -0,0 +1,55 @@
+% this is just being used as a namespace
+classdef printer
+ methods (Static = true)
+ function str = pr_str(obj, print_readably)
+ switch class(obj)
+ case 'types.Symbol'
+ str = obj.name;
+ case 'double'
+ str = num2str(obj);
+ case 'char'
+ if types.keyword_Q(obj)
+ str = sprintf(':%s', obj(2:end));
+ else
+ if print_readably
+ str = strrep(obj, '\', '\\');
+ str = strrep(str, '"', '\"');
+ str = strrep(str, char(10), '\n');
+ str = sprintf('"%s"', str);
+ else
+ str = obj;
+ end
+ end
+ case 'types.List'
+ strs = cellfun(@(x) printer.pr_str(x, print_readably), ...
+ obj.data, 'UniformOutput', false);
+ str = sprintf('(%s)', strjoin(strs, ' '));
+ case 'types.Vector'
+ strs = cellfun(@(x) printer.pr_str(x, print_readably), ...
+ obj.data, 'UniformOutput', false);
+ str = sprintf('[%s]', strjoin(strs, ' '));
+ case 'types.HashMap'
+ strs = {};
+ ks = obj.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ strs{end+1} = printer.pr_str(k, print_readably);
+ strs{end+1} = printer.pr_str(obj.get(k), print_readably);
+ end
+ str = sprintf('{%s}', strjoin(strs, ' '));
+ case 'types.Nil'
+ str = 'nil';
+ case 'logical'
+ if eq(obj, true)
+ str = 'true';
+ else
+ str = 'false';
+ end
+ case 'types.Atom'
+ str = sprintf('(atom %s)', printer.pr_str(obj.val,true));
+ otherwise
+ str = '#<unknown>';
+ end
+ end
+ end
+end
diff --git a/matlab/reader.m b/matlab/reader.m
new file mode 100644
index 0000000..e0018ca
--- /dev/null
+++ b/matlab/reader.m
@@ -0,0 +1,122 @@
+% this is just being used as a namespace
+classdef reader
+ methods (Static = true)
+ function tokens = tokenize(str)
+ re = '[\s,]*(~@|[\[\]{}()''`~^@]|"(?:\\.|[^\\"])*"|;[^\n]*|[^\s\[\]{}(''"`,;)]*)';
+ % extract the capture group (to ignore spaces and commas)
+ tokens = cellfun(@(x) x(1), regexp(str, re, 'tokens'));
+ comments = cellfun(@(x) length(x) > 0 && x(1) == ';', tokens);
+ tokens = tokens(~comments);
+ end
+
+ function atm = read_atom(rdr)
+ token = rdr.next();
+ %fprintf('in read_atom: %s\n', token);
+ if not(isempty(regexp(token, '^-?[0-9]+$', 'match')))
+ atm = str2double(token);
+ elseif strcmp(token(1), '"')
+ atm = token(2:length(token)-1);
+ atm = strrep(atm, '\"', '"');
+ atm = strrep(atm, '\n', char(10));
+ elseif strcmp(token(1), ':')
+ atm = types.keyword(token);
+ elseif strcmp(token, 'nil')
+ atm = types.nil;
+ elseif strcmp(token, 'true')
+ atm = true;
+ elseif strcmp(token, 'false')
+ atm = false;
+ else
+ atm = types.Symbol(token);
+ end
+ end
+
+ function seq = read_seq(rdr, start, last)
+ %fprintf('in read_seq\n');
+ seq = {};
+ token = rdr.next();
+ if not(strcmp(token, start))
+ error(sprintf('expected ''%s''', start));
+ end
+ token = rdr.peek();
+ while true
+ if eq(token, false)
+ error(sprintf('expected ''%s''', last));
+ end
+ if strcmp(token, last), break, end
+ seq{end+1} = reader.read_form(rdr);
+ token = rdr.peek();
+ end
+ rdr.next();
+ end
+
+ function lst = read_list(rdr)
+ seq = reader.read_seq(rdr, '(', ')');
+ lst = types.List(seq{:});
+ end
+
+ function vec = read_vector(rdr)
+ seq = reader.read_seq(rdr, '[', ']');
+ vec = types.Vector(seq{:});
+ end
+
+ function map = read_hash_map(rdr)
+ seq = reader.read_seq(rdr, '{', '}');
+ map = types.HashMap(seq{:});
+ end
+
+ function ast = read_form(rdr)
+ %fprintf('in read_form\n');
+ token = rdr.peek();
+ switch token
+ case ''''
+ rdr.next();
+ ast = types.List(types.Symbol('quote'), ...
+ reader.read_form(rdr));
+ case '`'
+ rdr.next();
+ ast = types.List(types.Symbol('quasiquote'), ...
+ reader.read_form(rdr));
+ case '~'
+ rdr.next();
+ ast = types.List(types.Symbol('unquote'), ...
+ reader.read_form(rdr));
+ case '~@'
+ rdr.next();
+ ast = types.List(types.Symbol('splice-unquote'), ...
+ reader.read_form(rdr));
+ case '^'
+ rdr.next();
+ meta = reader.read_form(rdr);
+ ast = types.List(types.Symbol('with-meta'), ...
+ reader.read_form(rdr), meta);
+ case '@'
+ rdr.next();
+ ast = types.List(types.Symbol('deref'), ...
+ reader.read_form(rdr));
+
+ case ')'
+ error('unexpected '')''');
+ case '('
+ ast = reader.read_list(rdr);
+ case ']'
+ error('unexpected '']''');
+ case '['
+ ast = reader.read_vector(rdr);
+ case '}'
+ error('unexpected ''}''');
+ case '{'
+ ast = reader.read_hash_map(rdr);
+ otherwise
+ ast = reader.read_atom(rdr);
+ end
+ end
+
+ function ast = read_str(str)
+ %fprintf('in read_str\n');
+ tokens = reader.tokenize(str);
+ rdr = types.Reader(tokens);
+ ast = reader.read_form(rdr);
+ end
+ end
+end
diff --git a/matlab/step0_repl.m b/matlab/step0_repl.m
new file mode 100644
index 0000000..3b4e7ca
--- /dev/null
+++ b/matlab/step0_repl.m
@@ -0,0 +1,28 @@
+function step0_repl(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = str;
+end
+
+% eval
+function ret = EVAL(ast, env)
+ ret = ast;
+end
+
+% print
+function ret = PRINT(ast)
+ ret = ast;
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ while (true)
+ line = input('user> ', 's');
+ fprintf('%s\n', rep(line, ''));
+ end
+end
diff --git a/matlab/step1_read_print.m b/matlab/step1_read_print.m
new file mode 100644
index 0000000..8b63b36
--- /dev/null
+++ b/matlab/step1_read_print.m
@@ -0,0 +1,35 @@
+function step1_read_print(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = EVAL(ast, env)
+ ret = ast;
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, ''));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step2_eval.m b/matlab/step2_eval.m
new file mode 100644
index 0000000..d742d6f
--- /dev/null
+++ b/matlab/step2_eval.m
@@ -0,0 +1,77 @@
+function step2_eval(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env(ast.name);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.data(2:end);
+ ret = f(args{:});
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = containers.Map();
+ repl_env('+') = @(a,b) a+b;
+ repl_env('-') = @(a,b) a-b;
+ repl_env('*') = @(a,b) a*b;
+ repl_env('/') = @(a,b) floor(a/b);
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step3_env.m b/matlab/step3_env.m
new file mode 100644
index 0000000..55a3668
--- /dev/null
+++ b/matlab/step3_env.m
@@ -0,0 +1,93 @@
+function step3_env(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ ret = EVAL(ast.get(3), let_env);
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.data(2:end);
+ ret = f(args{:});
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+ repl_env.set(types.Symbol('+'), @(a,b) a+b);
+ repl_env.set(types.Symbol('-'), @(a,b) a-b);
+ repl_env.set(types.Symbol('*'), @(a,b) a*b);
+ repl_env.set(types.Symbol('/'), @(a,b) floor(a/b));
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step4_if_fn_do.m b/matlab/step4_if_fn_do.m
new file mode 100644
index 0000000..a864194
--- /dev/null
+++ b/matlab/step4_if_fn_do.m
@@ -0,0 +1,117 @@
+function step4_if_fn_do(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ ret = EVAL(ast.get(3), let_env);
+ case 'do'
+ el = eval_ast(ast.slice(2), env);
+ ret = el.get(length(el));
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ret = EVAL(ast.get(4), env);
+ else
+ ret = types.nil;
+ end
+ else
+ ret = EVAL(ast.get(3), env);
+ end
+ case 'fn*'
+ ret = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.data(2:end);
+ ret = f(args{:});
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+
+ % core.mal: defined using the langauge itself
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step5_tco.m b/matlab/step5_tco.m
new file mode 100644
index 0000000..c0c46f6
--- /dev/null
+++ b/matlab/step5_tco.m
@@ -0,0 +1,130 @@
+function step5_tco(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ while true
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ return;
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ env = let_env;
+ ast = ast.get(3); % TCO
+ case 'do'
+ el = eval_ast(ast.slice(2,length(ast)-1), env);
+ ast = ast.get(length(ast)); % TCO
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ast = ast.get(4); % TCO
+ else
+ ret = types.nil;
+ return;
+ end
+ else
+ ast = ast.get(3); % TCO
+ end
+ case 'fn*'
+ fn = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ ret = types.Function(fn, ast.get(3), env, ast.get(2));
+ return;
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.slice(2);
+ if isa(f, 'types.Function')
+ env = Env(f.env, f.params, args);
+ ast = f.ast; % TCO
+ else
+ ret = f(args.data{:});
+ return
+ end
+ end
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+
+ % core.mal: defined using the langauge itself
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step6_file.m b/matlab/step6_file.m
new file mode 100644
index 0000000..6a9476d
--- /dev/null
+++ b/matlab/step6_file.m
@@ -0,0 +1,139 @@
+function step6_file(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ while true
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ return;
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ env = let_env;
+ ast = ast.get(3); % TCO
+ case 'do'
+ el = eval_ast(ast.slice(2,length(ast)-1), env);
+ ast = ast.get(length(ast)); % TCO
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ast = ast.get(4); % TCO
+ else
+ ret = types.nil;
+ return;
+ end
+ else
+ ast = ast.get(3); % TCO
+ end
+ case 'fn*'
+ fn = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ ret = types.Function(fn, ast.get(3), env, ast.get(2));
+ return;
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.slice(2);
+ if isa(f, 'types.Function')
+ env = Env(f.env, f.params, args);
+ ast = f.ast; % TCO
+ else
+ ret = f(args.data{:});
+ return
+ end
+ end
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+ repl_env.set(types.Symbol('eval'), @(a) EVAL(a, repl_env));
+ rest_args = args(2:end);
+ repl_env.set(types.Symbol('*ARGV*'), types.List(rest_args{:}));
+
+ % core.mal: defined using the langauge itself
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+ rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))"', repl_env);
+
+ if ~isempty(args)
+ rep(sprintf('(load-file "%s")', args{1}), repl_env);
+ quit;
+ end
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step7_quote.m b/matlab/step7_quote.m
new file mode 100644
index 0000000..3d1149a
--- /dev/null
+++ b/matlab/step7_quote.m
@@ -0,0 +1,167 @@
+function step7_quote(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = is_pair(ast)
+ ret = types.sequential_Q(ast) && length(ast) > 0;
+end
+
+function ret = quasiquote(ast)
+ if ~is_pair(ast)
+ ret = types.List(types.Symbol('quote'), ast);
+ elseif isa(ast.get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).name, 'unquote')
+ ret = ast.get(2);
+ elseif is_pair(ast.get(1)) && ...
+ isa(ast.get(1).get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).get(1).name, 'splice-unquote')
+ ret = types.List(types.Symbol('concat'), ...
+ ast.get(1).get(2), ...
+ quasiquote(ast.slice(2)));
+ else
+ ret = types.List(types.Symbol('cons'), ...
+ quasiquote(ast.get(1)), ...
+ quasiquote(ast.slice(2)));
+ end
+end
+
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ while true
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ return;
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ env = let_env;
+ ast = ast.get(3); % TCO
+ case 'quote'
+ ret = ast.get(2);
+ return;
+ case 'quasiquote'
+ ast = quasiquote(ast.get(2)); % TCO
+ case 'do'
+ el = eval_ast(ast.slice(2,length(ast)-1), env);
+ ast = ast.get(length(ast)); % TCO
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ast = ast.get(4); % TCO
+ else
+ ret = types.nil;
+ return;
+ end
+ else
+ ast = ast.get(3); % TCO
+ end
+ case 'fn*'
+ fn = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ ret = types.Function(fn, ast.get(3), env, ast.get(2));
+ return;
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.slice(2);
+ if isa(f, 'types.Function')
+ env = Env(f.env, f.params, args);
+ ast = f.ast; % TCO
+ else
+ ret = f(args.data{:});
+ return
+ end
+ end
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+ repl_env.set(types.Symbol('eval'), @(a) EVAL(a, repl_env));
+ rest_args = args(2:end);
+ repl_env.set(types.Symbol('*ARGV*'), types.List(rest_args{:}));
+
+ % core.mal: defined using the langauge itself
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+ rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))"', repl_env);
+
+ if ~isempty(args)
+ rep(sprintf('(load-file "%s")', args{1}), repl_env);
+ quit;
+ end
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step8_macros.m b/matlab/step8_macros.m
new file mode 100644
index 0000000..5567649
--- /dev/null
+++ b/matlab/step8_macros.m
@@ -0,0 +1,201 @@
+function step8_macros(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = is_pair(ast)
+ ret = types.sequential_Q(ast) && length(ast) > 0;
+end
+
+function ret = quasiquote(ast)
+ if ~is_pair(ast)
+ ret = types.List(types.Symbol('quote'), ast);
+ elseif isa(ast.get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).name, 'unquote')
+ ret = ast.get(2);
+ elseif is_pair(ast.get(1)) && ...
+ isa(ast.get(1).get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).get(1).name, 'splice-unquote')
+ ret = types.List(types.Symbol('concat'), ...
+ ast.get(1).get(2), ...
+ quasiquote(ast.slice(2)));
+ else
+ ret = types.List(types.Symbol('cons'), ...
+ quasiquote(ast.get(1)), ...
+ quasiquote(ast.slice(2)));
+ end
+end
+
+function ret = is_macro_call(ast, env)
+ if types.list_Q(ast) && isa(ast.get(1), 'types.Symbol') && ...
+ ~islogical(env.find(ast.get(1)))
+ f = env.get(ast.get(1));
+ ret = isa(f,'types.Function') && f.is_macro;
+ else
+ ret = false;
+ end
+end
+
+function ret = macroexpand(ast, env)
+ while is_macro_call(ast, env)
+ mac = env.get(ast.get(1));
+ args = ast.slice(2);
+ ast = mac.fn(args.data{:});
+ end
+ ret = ast;
+end
+
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ while true
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ ast = macroexpand(ast, env);
+ if ~types.list_Q(ast)
+ ret = ast;
+ return;
+ end
+
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ return;
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ env = let_env;
+ ast = ast.get(3); % TCO
+ case 'quote'
+ ret = ast.get(2);
+ return;
+ case 'quasiquote'
+ ast = quasiquote(ast.get(2)); % TCO
+ case 'defmacro!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ ret.is_macro = true;
+ return;
+ case 'macroexpand'
+ ret = macroexpand(ast.get(2), env);
+ return;
+ case 'do'
+ el = eval_ast(ast.slice(2,length(ast)-1), env);
+ ast = ast.get(length(ast)); % TCO
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ast = ast.get(4); % TCO
+ else
+ ret = types.nil;
+ return;
+ end
+ else
+ ast = ast.get(3); % TCO
+ end
+ case 'fn*'
+ fn = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ ret = types.Function(fn, ast.get(3), env, ast.get(2));
+ return;
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.slice(2);
+ if isa(f, 'types.Function')
+ env = Env(f.env, f.params, args);
+ ast = f.ast; % TCO
+ else
+ ret = f(args.data{:});
+ return
+ end
+ end
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+ repl_env.set(types.Symbol('eval'), @(a) EVAL(a, repl_env));
+ rest_args = args(2:end);
+ repl_env.set(types.Symbol('*ARGV*'), types.List(rest_args{:}));
+
+ % core.mal: defined using the langauge itself
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+ rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))"', repl_env);
+ 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)))))))', repl_env);
+ 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))))))))', repl_env);
+
+ if ~isempty(args)
+ rep(sprintf('(load-file "%s")', args{1}), repl_env);
+ quit;
+ end
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ fprintf('Error: %s\n', err.message);
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/step9_try.m b/matlab/step9_try.m
new file mode 100644
index 0000000..1f338b2
--- /dev/null
+++ b/matlab/step9_try.m
@@ -0,0 +1,224 @@
+function step9_try(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = is_pair(ast)
+ ret = types.sequential_Q(ast) && length(ast) > 0;
+end
+
+function ret = quasiquote(ast)
+ if ~is_pair(ast)
+ ret = types.List(types.Symbol('quote'), ast);
+ elseif isa(ast.get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).name, 'unquote')
+ ret = ast.get(2);
+ elseif is_pair(ast.get(1)) && ...
+ isa(ast.get(1).get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).get(1).name, 'splice-unquote')
+ ret = types.List(types.Symbol('concat'), ...
+ ast.get(1).get(2), ...
+ quasiquote(ast.slice(2)));
+ else
+ ret = types.List(types.Symbol('cons'), ...
+ quasiquote(ast.get(1)), ...
+ quasiquote(ast.slice(2)));
+ end
+end
+
+function ret = is_macro_call(ast, env)
+ if types.list_Q(ast) && isa(ast.get(1), 'types.Symbol') && ...
+ ~islogical(env.find(ast.get(1)))
+ f = env.get(ast.get(1));
+ ret = isa(f,'types.Function') && f.is_macro;
+ else
+ ret = false;
+ end
+end
+
+function ret = macroexpand(ast, env)
+ while is_macro_call(ast, env)
+ mac = env.get(ast.get(1));
+ args = ast.slice(2);
+ ast = mac.fn(args.data{:});
+ end
+ ret = ast;
+end
+
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ while true
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ ast = macroexpand(ast, env);
+ if ~types.list_Q(ast)
+ ret = ast;
+ return;
+ end
+
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ return;
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ env = let_env;
+ ast = ast.get(3); % TCO
+ case 'quote'
+ ret = ast.get(2);
+ return;
+ case 'quasiquote'
+ ast = quasiquote(ast.get(2)); % TCO
+ case 'defmacro!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ ret.is_macro = true;
+ return;
+ case 'macroexpand'
+ ret = macroexpand(ast.get(2), env);
+ return;
+ case 'try*'
+ try
+ ret = EVAL(ast.get(2), env);
+ return;
+ catch e
+ if length(ast) > 2 && strcmp(ast.get(3).get(1).name, 'catch*')
+ if isa(e, 'types.MalException')
+ exc = e.obj;
+ else
+ exc = e.message;
+ end
+ catch_env = Env(env, types.List(ast.get(3).get(2)), ...
+ types.List(exc));
+ ret = EVAL(ast.get(3).get(3), catch_env);
+ return;
+ else
+ throw(e);
+ end
+ end
+ case 'do'
+ el = eval_ast(ast.slice(2,length(ast)-1), env);
+ ast = ast.get(length(ast)); % TCO
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ast = ast.get(4); % TCO
+ else
+ ret = types.nil;
+ return;
+ end
+ else
+ ast = ast.get(3); % TCO
+ end
+ case 'fn*'
+ fn = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ ret = types.Function(fn, ast.get(3), env, ast.get(2));
+ return;
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.slice(2);
+ if isa(f, 'types.Function')
+ env = Env(f.env, f.params, args);
+ ast = f.ast; % TCO
+ else
+ ret = f(args.data{:});
+ return
+ end
+ end
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+ repl_env.set(types.Symbol('eval'), @(a) EVAL(a, repl_env));
+ rest_args = args(2:end);
+ repl_env.set(types.Symbol('*ARGV*'), types.List(rest_args{:}));
+
+ % core.mal: defined using the langauge itself
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+ rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))"', repl_env);
+ 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)))))))', repl_env);
+ 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))))))))', repl_env);
+
+ if ~isempty(args)
+ rep(sprintf('(load-file "%s")', args{1}), repl_env);
+ quit;
+ end
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ if isa(err, 'types.MalException')
+ fprintf('Error: %s\n', printer.pr_str(err.obj, true));
+ else
+ fprintf('Error: %s\n', err.message);
+ end
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/stepA_mal.m b/matlab/stepA_mal.m
new file mode 100644
index 0000000..6f74b6a
--- /dev/null
+++ b/matlab/stepA_mal.m
@@ -0,0 +1,226 @@
+function stepA_mal(varargin), main(varargin), end
+
+% read
+function ret = READ(str)
+ ret = reader.read_str(str);
+end
+
+% eval
+function ret = is_pair(ast)
+ ret = types.sequential_Q(ast) && length(ast) > 0;
+end
+
+function ret = quasiquote(ast)
+ if ~is_pair(ast)
+ ret = types.List(types.Symbol('quote'), ast);
+ elseif isa(ast.get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).name, 'unquote')
+ ret = ast.get(2);
+ elseif is_pair(ast.get(1)) && ...
+ isa(ast.get(1).get(1),'types.Symbol') && ...
+ strcmp(ast.get(1).get(1).name, 'splice-unquote')
+ ret = types.List(types.Symbol('concat'), ...
+ ast.get(1).get(2), ...
+ quasiquote(ast.slice(2)));
+ else
+ ret = types.List(types.Symbol('cons'), ...
+ quasiquote(ast.get(1)), ...
+ quasiquote(ast.slice(2)));
+ end
+end
+
+function ret = is_macro_call(ast, env)
+ if types.list_Q(ast) && isa(ast.get(1), 'types.Symbol') && ...
+ ~islogical(env.find(ast.get(1)))
+ f = env.get(ast.get(1));
+ ret = isa(f,'types.Function') && f.is_macro;
+ else
+ ret = false;
+ end
+end
+
+function ret = macroexpand(ast, env)
+ while is_macro_call(ast, env)
+ mac = env.get(ast.get(1));
+ args = ast.slice(2);
+ ast = mac.fn(args.data{:});
+ end
+ ret = ast;
+end
+
+function ret = eval_ast(ast, env)
+ switch class(ast)
+ case 'types.Symbol'
+ ret = env.get(ast);
+ case 'types.List'
+ ret = types.List();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.Vector'
+ ret = types.Vector();
+ for i=1:length(ast)
+ ret.append(EVAL(ast.get(i), env));
+ end
+ case 'types.HashMap'
+ ret = types.HashMap();
+ ks = ast.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ ret.set(EVAL(k, env), EVAL(ast.get(k), env));
+ end
+ otherwise
+ ret = ast;
+ end
+end
+
+function ret = EVAL(ast, env)
+ while true
+ %fprintf('EVAL: %s\n', printer.pr_str(ast, true));
+ if ~types.list_Q(ast)
+ ret = eval_ast(ast, env);
+ return;
+ end
+
+ % apply
+ ast = macroexpand(ast, env);
+ if ~types.list_Q(ast)
+ ret = ast;
+ return;
+ end
+
+ if isa(ast.get(1),'types.Symbol')
+ a1sym = ast.get(1).name;
+ else
+ a1sym = '_@$fn$@_';
+ end
+ switch (a1sym)
+ case 'def!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ return;
+ case 'let*'
+ let_env = Env(env);
+ for i=1:2:length(ast.get(2))
+ let_env.set(ast.get(2).get(i), EVAL(ast.get(2).get(i+1), let_env));
+ end
+ env = let_env;
+ ast = ast.get(3); % TCO
+ case 'quote'
+ ret = ast.get(2);
+ return;
+ case 'quasiquote'
+ ast = quasiquote(ast.get(2)); % TCO
+ case 'defmacro!'
+ ret = env.set(ast.get(2), EVAL(ast.get(3), env));
+ ret.is_macro = true;
+ return;
+ case 'macroexpand'
+ ret = macroexpand(ast.get(2), env);
+ return;
+ case 'try*'
+ try
+ ret = EVAL(ast.get(2), env);
+ return;
+ catch e
+ if length(ast) > 2 && strcmp(ast.get(3).get(1).name, 'catch*')
+ if isa(e, 'types.MalException')
+ exc = e.obj;
+ else
+ exc = e.message;
+ end
+ catch_env = Env(env, types.List(ast.get(3).get(2)), ...
+ types.List(exc));
+ ret = EVAL(ast.get(3).get(3), catch_env);
+ return;
+ else
+ throw(e);
+ end
+ end
+ case 'do'
+ el = eval_ast(ast.slice(2,length(ast)-1), env);
+ ast = ast.get(length(ast)); % TCO
+ case 'if'
+ cond = EVAL(ast.get(2), env);
+ if strcmp(class(cond), 'types.Nil') || ...
+ (islogical(cond) && cond == false)
+ if length(ast) > 3
+ ast = ast.get(4); % TCO
+ else
+ ret = types.nil;
+ return;
+ end
+ else
+ ast = ast.get(3); % TCO
+ end
+ case 'fn*'
+ fn = @(varargin) EVAL(ast.get(3), Env(env, ast.get(2), ...
+ types.List(varargin{:})));
+ ret = types.Function(fn, ast.get(3), env, ast.get(2));
+ return;
+ otherwise
+ el = eval_ast(ast, env);
+ f = el.get(1);
+ args = el.slice(2);
+ if isa(f, 'types.Function')
+ env = Env(f.env, f.params, args);
+ ast = f.ast; % TCO
+ else
+ ret = f(args.data{:});
+ return
+ end
+ end
+ end
+end
+
+% print
+function ret = PRINT(ast)
+ ret = printer.pr_str(ast, true);
+end
+
+% REPL
+function ret = rep(str, env)
+ ret = PRINT(EVAL(READ(str), env));
+end
+
+function main(args)
+ repl_env = Env(false);
+
+ % core.m: defined using matlab
+ ns = core.ns(); ks = ns.keys();
+ for i=1:length(ks)
+ k = ks{i};
+ repl_env.set(types.Symbol(k), ns(k));
+ end
+ repl_env.set(types.Symbol('eval'), @(a) EVAL(a, repl_env));
+ rest_args = args(2:end);
+ repl_env.set(types.Symbol('*ARGV*'), types.List(rest_args{:}));
+
+ % core.mal: defined using the langauge itself
+ rep('(def! *host-language* "matlab")', repl_env);
+ rep('(def! not (fn* (a) (if a false true)))', repl_env);
+ rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))"', repl_env);
+ 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)))))))', repl_env);
+ 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))))))))', repl_env);
+
+ if ~isempty(args)
+ rep(sprintf('(load-file "%s")', args{1}), repl_env);
+ quit;
+ end
+
+ %cleanObj = onCleanup(@() disp('*** here1 ***'));
+ rep('(println (str "Mal [" *host-language* "]"))', repl_env);
+ while (true)
+ line = input('user> ', 's');
+ if strcmp(strtrim(line),''), continue, end
+ try
+ fprintf('%s\n', rep(line, repl_env));
+ catch err
+ if isa(err, 'types.MalException')
+ fprintf('Error: %s\n', printer.pr_str(err.obj, true));
+ else
+ fprintf('Error: %s\n', err.message);
+ end
+ fprintf('%s\n', getReport(err, 'extended'));
+ end
+ end
+end
diff --git a/matlab/types b/matlab/types
new file mode 120000
index 0000000..bb26791
--- /dev/null
+++ b/matlab/types
@@ -0,0 +1 @@
++types/ \ No newline at end of file
diff --git a/matlab/types.m b/matlab/types.m
new file mode 100644
index 0000000..4d78f91
--- /dev/null
+++ b/matlab/types.m
@@ -0,0 +1,57 @@
+classdef types
+ properties (Constant = true)
+ nil = types.Nil();
+ end
+
+ methods(Static)
+ function ret = equal(a,b)
+ ret = false;
+ ota = class(a); otb = class(b);
+ if ~(strcmp(ota,otb) || ...
+ (types.sequential_Q(a) && types.sequential_Q(b)))
+ return;
+ end
+ switch (ota)
+ case {'types.List', 'types.Vector'}
+ if ~(length(a) == length(b))
+ return;
+ end
+ for i=1:length(a)
+ if ~(types.equal(a.get(i), b.get(i)))
+ return;
+ end
+ end
+ ret = true;
+ case 'char'
+ ret = strcmp(a,b);
+ otherwise
+ ret = a == b;
+ end
+ end
+
+ function ret = sequential_Q(obj)
+ ret = strcmp(class(obj), 'types.List') || ...
+ strcmp(class(obj), 'types.Vector');
+ end
+
+ function ret = list_Q(obj)
+ ret = strcmp(class(obj), 'types.List');
+ end
+ function ret = vector_Q(obj)
+ ret = strcmp(class(obj), 'types.Vector');
+ end
+ function ret = hash_map_Q(obj)
+ ret = strcmp(class(obj), 'types.HashMap');
+ end
+
+ function ret = keyword(str)
+ ret = sprintf('%s%s', native2unicode(hex2dec('029e'),'UTF-8'), ...
+ str(2:end));
+ end
+ function ret = keyword_Q(obj)
+ ret = length(obj) > 1 && ...
+ strcmp(obj(1), native2unicode(hex2dec('029e'),'UTF-8'));
+ end
+ end
+end
+
diff --git a/miniMAL/Makefile b/miniMAL/Makefile
new file mode 100644
index 0000000..8c838d3
--- /dev/null
+++ b/miniMAL/Makefile
@@ -0,0 +1,12 @@
+
+SOURCES_BASE = node_readline.js miniMAL-core.json \
+ types.json reader.json printer.json
+SOURCES_LISP = env.json core.json stepA_mal.json
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/miniMAL/core.json b/miniMAL/core.json
new file mode 100644
index 0000000..164a846
--- /dev/null
+++ b/miniMAL/core.json
@@ -0,0 +1,154 @@
+["do",
+
+["def", "_path", ["require", ["`", "path"]]],
+
+["def", "_node_readline", ["require", [".", "_path", ["`", "resolve"],
+ ["`", "."],
+ ["`", "node_readline.js"]]]],
+
+["def", "div", ["fn", ["a", "b"], ["parseInt", ["/", "a", "b"]]]],
+
+["def", "time-ms", ["fn", [],
+ [".", ["new", "Date"], ["`", "getTime"]]]],
+
+
+["def", "assoc", ["fn", ["src-hm", "&", "kvs"],
+ ["let", ["hm", ["clone", "src-hm"]],
+ ["assocs!", "hm", "kvs"]]]],
+
+["def", "dissoc", ["fn", ["src-hm", "&", "ks"],
+ ["let", ["hm", ["clone", "src-hm"]],
+ ["do",
+ ["map", ["fn", ["k"], ["del", "hm", "k"]], "ks"],
+ "hm"]]]],
+
+["def", "_get", ["fn", ["obj", "key"],
+ ["if", ["nil?", "obj"],
+ null,
+ ["if", ["contains?", "obj", "key"],
+ ["get", "obj", "key"],
+ null]]]],
+
+["def", "_count", ["fn", ["a"],
+ ["if", ["=", null, "a"],
+ 0,
+ ["count", "a"]]]],
+
+["def", "_nth", ["fn", ["seq", "idx"],
+ ["if", [">=", "idx", ["count", "seq"]],
+ ["throw", "nth: index out of range"],
+ ["nth", "seq", "idx"]]]],
+
+["def", "_first", ["fn", ["seq"],
+ ["if", ["empty?", "seq"],
+ null,
+ ["first", "seq"]]]],
+
+["def", "_apply", ["fn", ["f", "&", "args"],
+ ["let", ["fn", ["if", ["malfunc?", "f"], ["get", "f", ["`", "fn"]], "f"],
+ "fargs", ["concat", ["slice", "args", 0, ["-", ["count", "args"], 1]],
+ ["nth", "args", ["-", ["count", "args"], 1]]]],
+ ["apply", "fn", "fargs"]]]],
+
+["def", "_map", ["fn", ["f", "seq"],
+ ["let", ["fn", ["if", ["malfunc?", "f"], ["get", "f", ["`", "fn"]], "f"]],
+ ["map", "fn", "seq"]]]],
+
+["def", "with_meta", ["fn", ["obj", "m"],
+ ["let", ["new-obj", ["clone", "obj"]],
+ ["do",
+ ["set", "new-obj", ["`", "__meta__"], "m"],
+ "new-obj"]]]],
+
+["def", "meta", ["fn", ["obj"],
+ ["if", ["or", ["sequential?", "obj"],
+ ["map?", "obj"],
+ ["malfunc?", "obj"]],
+ ["if", ["contains?", "obj", ["`", "__meta__"]],
+ ["get", "obj", ["`", "__meta__"]],
+ null],
+ null]]],
+
+["def", "reset!", ["fn", ["atm", "val"],
+ ["set", "atm", ["`", "val"], "val"]]],
+
+["def", "swap!", ["fn", ["atm", "f", "&", "args"],
+ ["let", ["fn", ["if", ["malfunc?", "f"], ["get", "f", ["`", "fn"]], "f"],
+ "fargs", ["cons", ["get", "atm", ["`", "val"]], "args"],
+ "val", ["apply", "fn", "fargs"]],
+ ["do",
+ ["set", "atm", ["`", "val"], "val"],
+ "val"]]]],
+
+["def", "core-ns",
+ ["hash-map",
+ ["`", "="], "equal?",
+ ["`", "throw"], "throw",
+
+ ["`", "nil?"], "nil?",
+ ["`", "true?"], "true?",
+ ["`", "false?"], "false?",
+ ["`", "symbol"], "symbol",
+ ["`", "symbol?"], "symbol?",
+ ["`", "keyword"], "keyword",
+ ["`", "keyword?"], "keyword?",
+
+ ["`", "pr-str"], ["fn", ["&", "a"], ["pr-list", "a", true, ["`", " "]]],
+ ["`", "str"], ["fn", ["&", "a"], ["pr-list", "a", false, ["`", ""]]],
+ ["`", "prn"], ["fn", ["&", "a"],
+ ["do",
+ ["println", ["pr-list", "a", true, ["`", " "]]],
+ null]],
+ ["`", "println"], ["fn", ["&", "a"],
+ ["do",
+ ["println", ["pr-list", "a", false, ["`", " "]]],
+ null]],
+ ["`", "read-string"], "read-str",
+ ["`", "readline"], ["fn", ["p"],
+ [".", "_node_readline", ["`", "readline"], "p"]],
+ ["`", "slurp"], "slurp",
+
+ ["`", "<"], "<",
+ ["`", "<="], "<=",
+ ["`", ">"], ">",
+ ["`", ">="], ">=",
+ ["`", "+"], "+",
+ ["`", "-"], "-",
+ ["`", "*"], "*",
+ ["`", "/"], "div",
+ ["`", "time-ms"], "time-ms",
+
+ ["`", "list"], "list",
+ ["`", "list?"], "list?",
+ ["`", "vector"], "vector",
+ ["`", "vector?"], "vector?",
+ ["`", "hash-map"], "hash-map",
+ ["`", "assoc"], "assoc",
+ ["`", "dissoc"], "dissoc",
+ ["`", "map?"], "map?",
+ ["`", "get"], "_get",
+ ["`", "contains?"], "contains?",
+ ["`", "keys"], "keys",
+ ["`", "vals"], "vals",
+
+ ["`", "sequential?"], "sequential?",
+ ["`", "cons"], "cons",
+ ["`", "concat"], "concat",
+ ["`", "nth"], "_nth",
+ ["`", "first"], "_first",
+ ["`", "rest"], ["fn", ["a"], ["rest", "a"]],
+ ["`", "empty?"], "empty?",
+ ["`", "count"], "_count",
+ ["`", "apply"], "_apply",
+ ["`", "map"], "_map",
+ ["`", "conj"], null,
+
+ ["`", "with-meta"], "with_meta",
+ ["`", "meta"], "meta",
+ ["`", "atom"], "atom",
+ ["`", "atom?"], "atom?",
+ ["`", "deref"], ["fn", ["a"], ["get", "a", ["`", "val"]]],
+ ["`", "reset!"], "reset!",
+ ["`", "swap!"], "swap!"]],
+
+null]
diff --git a/miniMAL/env.json b/miniMAL/env.json
new file mode 100644
index 0000000..0dfa67b
--- /dev/null
+++ b/miniMAL/env.json
@@ -0,0 +1,42 @@
+["do",
+
+["def", "env-bind", ["fn", ["env", "b", "e"],
+ ["if", ["empty?", "b"],
+ "env",
+ ["if", ["=", ["`", "&"],
+ ["get", ["first", "b"], ["`", "val"]]],
+ ["assoc!", "env", ["get", ["nth", "b", 1], ["`", "val"]], "e"],
+ ["env-bind", ["assoc!", "env", ["get", ["first", "b"], ["`", "val"]],
+ ["first", "e"]],
+ ["rest", "b"],
+ ["rest", "e"]]]]]],
+
+["def", "env-new", ["fn", ["&", "args"],
+ ["let", ["env", ["hash-map", ["`", "__outer__"], ["first", "args"]]],
+ ["if", ["<=", ["count", "args"], 1],
+ "env",
+ ["env-bind", "env", ["get", "args", 1], ["get", "args", 2]]]]]],
+
+["def", "env-find", ["fn", ["env", "key"],
+ ["let", ["k", ["get", "key", ["`", "val"]]],
+ ["if", ["contains?", "env", "k"],
+ "env",
+ ["if", ["get", "env", ["`", "__outer__"]],
+ ["env-find", ["get", "env", ["`", "__outer__"]], "key"],
+ null]]]]],
+
+["def", "env-get", ["fn", ["env", "key"],
+ ["let", ["k", ["get", "key", ["`", "val"]],
+ "e", ["env-find", "env", "key"]],
+ ["if", "e",
+ ["get", "e", "k"],
+ ["throw", ["str", ["`", "'"], "k", ["`", "' not found"]]]]]]],
+
+["def", "env-set", ["fn", ["env", "key", "val"],
+ ["let", ["k", ["get", "key", ["`", "val"]]],
+ ["do",
+ ["assoc!", "env", "k", "val"],
+ "val"]]]],
+
+null
+]
diff --git a/miniMAL/miniMAL-core.json b/miniMAL/miniMAL-core.json
new file mode 100644
index 0000000..c22376a
--- /dev/null
+++ b/miniMAL/miniMAL-core.json
@@ -0,0 +1,111 @@
+["do",
+
+["def", "map", ["fn", ["a", "b"], [".", "b", ["`", "map"], "a"]]],
+["def", "not", ["fn", ["a"], ["if", "a", false, true]]],
+
+["def", "nil?", ["fn", ["a"], ["=", null, "a"]]],
+["def", "true?", ["fn", ["a"], ["=", true, "a"]]],
+["def", "false?", ["fn", ["a"], ["=", false, "a"]]],
+["def", "string?", ["fn", ["a"],
+ ["if", ["=", "a", null],
+ false,
+ ["=", ["`", "String"],
+ [".-", [".-", "a", ["`", "constructor"]],
+ ["`", "name"]]]]]],
+
+["def", "pr-list*", ["fn", ["a", "pr", "sep"],
+ [".", ["map", ["fn", ["x"],
+ ["if", "pr",
+ [".", "JSON", ["`", "stringify"], "x"],
+ ["if", ["string?", "x"],
+ "x",
+ [".", "JSON", ["`", "stringify"], "x"]]]],
+ "a"],
+ ["`", "join"], "sep"]]],
+["def", "pr-str", ["fn", ["&", "a"],
+ ["pr-list*", "a", true, ["`", " "]]]],
+["def", "str", ["fn", ["&", "a"],
+ ["pr-list*", "a", false, ["`", ""]]]],
+["def", "prn", ["fn", ["&", "a"],
+ [".", "console", ["`", "log"],
+ ["pr-list*", "a", true, ["`", " "]]]]],
+["def", "println", ["fn", ["&", "a"],
+ [".", "console", ["`", "log"],
+ ["pr-list*", "a", false, ["`", " "]]]]],
+
+["def", ">=", ["fn", ["a", "b"],
+ ["if", ["<", "a", "b"], false, true]]],
+["def", ">", ["fn", ["a", "b"],
+ ["if", [">=", "a", "b"], ["if", ["=", "a", "b"], false, true], false]]],
+["def", "<=", ["fn", ["a", "b"],
+ ["if", [">", "a", "b"], false, true]]],
+
+["def", "list", ["fn", ["&", "a"], "a"]],
+["def", "list?", ["fn", ["a"], [".", "Array", ["`", "isArray"], "a"]]],
+["def", "get", ["fn", ["a", "b"], [".-", "a", "b"]]],
+["def", "set", ["fn", ["a", "b", "c"], [".-", "a", "b", "c"]]],
+["def", "contains?", ["fn", ["a", "b"], [".", "a", ["`", "hasOwnProperty"], "b"]]],
+["def", "keys", ["fn", ["a"], [".", "Object", ["`", "keys"], "a"]]],
+["def", "vals", ["fn", ["a"], ["map",["fn",["k"],["get","a","k"]],["keys", "a"]]]],
+
+["def", "cons", ["fn", ["a", "b"],
+ [".", ["`", []],
+ ["`", "concat"], ["list", "a"], "b"]]],
+["def", "concat", ["fn", ["&", "a"],
+ [".", [".-", ["list"], ["`", "concat"]],
+ ["`", "apply"], ["list"], "a"]]],
+["def", "nth", "get"],
+["def", "first", ["fn", ["a"], ["nth", "a", 0]]],
+["def", "rest", ["fn", ["a"], [".", "a", ["`", "slice"], 1]]],
+["def", "empty?", ["fn", ["a"], ["if", ["list?", "a"], ["if", ["=", 0, [".-", "a", ["`", "length"]]], true, false], ["=", "a", null]]]],
+["def", "count", ["fn", ["a"],
+ [".-", "a", ["`", "length"]]]],
+["def", "slice", ["fn", ["a", "start", "&", "endl"],
+ ["let", ["end", ["if", ["count", "endl"],
+ ["get", "endl", 0],
+ [".-", "a", ["`", "length"]]]],
+ [".", "a", ["`", "slice"], "start", "end"]]]],
+
+["def", "apply", ["fn", ["a", "b"], [".", "a", ["`", "apply"], "a", "b"]]],
+
+["def", "and", ["~", ["fn", ["&", "xs"],
+ ["if", ["empty?", "xs"],
+ true,
+ ["if", ["=", 1, ["count", "xs"]],
+ ["first", "xs"],
+ ["list", ["`", "let"], ["list", ["`", "and_FIXME"], ["first", "xs"]],
+ ["list", ["`", "if"], ["`", "and_FIXME"],
+ ["concat", ["`", ["and"]], ["rest", "xs"]],
+ ["`", "and_FIXME"]]]]]]]],
+
+["def", "or", ["~", ["fn", ["&", "xs"],
+ ["if", ["empty?", "xs"],
+ null,
+ ["if", ["=", 1, ["count", "xs"]],
+ ["first", "xs"],
+ ["list", ["`", "let"], ["list", ["`", "or_FIXME"], ["first", "xs"]],
+ ["list", ["`", "if"], ["`", "or_FIXME"],
+ ["`", "or_FIXME"],
+ ["concat", ["`", ["or"]], ["rest", "xs"]]]]]]]]],
+
+["def", "classOf", ["fn", ["a"],
+ [".", [".-", [".-", "Object", ["`", "prototype"]], ["`", "toString"]],
+ ["`", "call"], "a"]]],
+
+
+["def", "repl", ["fn",["prompt", "rep"],
+ ["let", ["r", ["require", ["`", "repl"]],
+ "evl", ["fn", ["l", "c", "f", "cb"],
+ ["let", ["line", ["slice", "l", 1, ["-", [".-", "l", ["`", "length"]], 2]]],
+ ["do",
+ ["println", ["rep", "line"]],
+ ["cb"]]]],
+ "opts", {"ignoreUndefined": true,
+ "terminal": false},
+ "opts", ["assoc!", "opts", ["`", "prompt"], "prompt"],
+ "opts", ["assoc!", "opts", ["`", "eval"], "evl"]],
+ [".", "r", ["`", "start"], "opts"]]]],
+
+null
+]
+
diff --git a/miniMAL/node_readline.js b/miniMAL/node_readline.js
new file mode 120000
index 0000000..7771772
--- /dev/null
+++ b/miniMAL/node_readline.js
@@ -0,0 +1 @@
+../js/node_readline.js \ No newline at end of file
diff --git a/miniMAL/package.json b/miniMAL/package.json
new file mode 100644
index 0000000..24d7a03
--- /dev/null
+++ b/miniMAL/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "mal-miniMAL",
+ "version": "0.0.1",
+ "description": "Make a Lisp (mal) language implemented in miniMAL",
+ "dependencies": {
+ "minimal-lisp": "0.0.3"
+ }
+}
diff --git a/miniMAL/printer.json b/miniMAL/printer.json
new file mode 100644
index 0000000..7463c78
--- /dev/null
+++ b/miniMAL/printer.json
@@ -0,0 +1,66 @@
+["do",
+
+["def", "pr-str", ["fn", ["exp", "print_readably"],
+ ["if", ["list?", "exp"],
+ ["str",
+ ["`", "("],
+ [".", ["map", ["fn", ["x"], ["pr-str", "x", "print_readably"]], "exp"],
+ ["`", "join"], ["`", " "]],
+ ["`", ")"]],
+ ["if", ["vector?", "exp"],
+ ["str",
+ ["`", "["],
+ [".", ["map", ["fn", ["x"], ["pr-str", "x", "print_readably"]], "exp"],
+ ["`", "join"], ["`", " "]],
+ ["`", "]"]],
+ ["if", ["map?", "exp"],
+ ["str",
+ ["`", "{"],
+ [".", ["map", ["fn", ["k"],
+ ["str", ["pr-str", "k", "print_readably"],
+ ["`", " "],
+ ["pr-str", ["get", "exp", "k"], "print_readably"]]],
+ ["keys", "exp"]],
+ ["`", "join"], ["`", " "]],
+ ["`", "}"]],
+ ["if", ["=", ["`", "[object String]"], ["classOf", "exp"]],
+ ["if", ["=", ["`", "\u029e"], ["get", "exp", 0]],
+ ["str", ["`", ":"], ["slice", "exp", 1]],
+ ["if", "print_readably",
+ ["str", ["`", "\""],
+ [".",
+ [".",
+ [".", "exp",
+ ["`", "replace"], ["RegExp", ["`", "\\\\"], ["`", "g"]], ["`", "\\\\"]],
+ ["`", "replace"], ["RegExp", ["`", "\""], ["`", "g"]], ["`", "\\\""]],
+ ["`", "replace"], ["RegExp", ["`", "\n"], ["`", "g"]], ["`", "\\n"]],
+ ["`", "\""]],
+ "exp"]],
+ ["if", ["=", ["`", "[object Number]"], ["classOf", "exp"]],
+ "exp",
+ ["if", ["=", null, "exp"],
+ ["`", "nil"],
+ ["if", ["=", true, "exp"],
+ ["`", "true"],
+ ["if", ["=", false, "exp"],
+ ["`", "false"],
+ ["if", ["symbol?", "exp"],
+ ["get", "exp", ["`", "val"]],
+ ["if", ["malfunc?", "exp"],
+ ["str", ["`", "(fn* "],
+ ["pr-str", ["get", "exp", ["`", "params"]]],
+ ["`", " "],
+ ["pr-str", ["get", "exp", ["`", "ast"]]],
+ ["`", ")"]],
+ ["if", ["=", ["`", "[object Function]"], ["classOf", "exp"]],
+ ["str", ["`", "#<native function>"]],
+ ["if", ["atom?", "exp"],
+ ["str", ["`", "(atom "], ["get", "exp", ["`", "val"]], ["`", ")"]],
+ ["str", ["`", "#<unknown: "], "exp", ["`", ">"]]]]]]]]]]]]]]]],
+
+["def", "pr-list", ["fn", ["lst", "print_readably", "sep"],
+ [".", ["map", ["fn", ["s"], ["pr-str", "s", "print_readably"]], "lst"],
+ ["`", "join"], "sep"]]],
+
+null
+]
diff --git a/miniMAL/reader.json b/miniMAL/reader.json
new file mode 100644
index 0000000..5fa113b
--- /dev/null
+++ b/miniMAL/reader.json
@@ -0,0 +1,126 @@
+["do",
+
+["def", "rdr-new", ["fn", ["tokens"],
+ ["hash-map", ["`", "tokens"], "tokens",
+ ["`", "position"], 0]]],
+
+["def", "rdr-next", ["fn", ["rdr"],
+ ["let", ["pos", ["get", "rdr", ["`", "position"]],
+ "val", ["get", ["get", "rdr", ["`", "tokens"]], "pos"]],
+ ["do",
+ ["assoc!", "rdr", ["`", "position"], ["+", 1, "pos"]],
+ "val"]]]],
+
+["def", "rdr-peek", ["fn", ["rdr"],
+ ["let", ["pos", ["get", "rdr", ["`", "position"]]],
+ ["get", ["get", "rdr", ["`", "tokens"]], "pos"]]]],
+
+
+["def", "re-matches", ["fn", ["re", "strn", "acc"],
+ ["let", ["match", [".", "re", ["`", "exec"], "strn"],
+ "g1", ["get", "match", 1]],
+ ["if", ["=", "g1", ["`", ""]],
+ "acc",
+ ["re-matches", "re", "strn", ["concat", "acc", "g1"]]]]]],
+
+["def", "tokenize", ["fn", ["strn"],
+ ["let", ["re-str", ["`", "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)"],
+ "re", ["RegExp", "re-str", ["`", "g"]]],
+ [".",
+ ["re-matches", "re", "strn", ["`", []]],
+ ["`", "filter"],
+ ["fn", ["x"], ["not", ["=", ["get", "x", 0],
+ ["`", ";"]]]]]]]],
+
+["def", "read-atom", ["fn", ["rdr"],
+ ["let", ["token", ["rdr-next", "rdr"]],
+ ["if", [".", "token", ["`", "match"], ["RegExp", ["`", "^-?[0-9]+$"]]],
+ ["parseInt", "token", 10],
+ ["if", ["=", ["`", "\""], ["get", "token", 0]],
+ [".",
+ [".",
+ ["slice", "token", 1, ["-", ["count", "token"], 1]],
+ ["`", "replace"], ["RegExp", ["`", "\\\\\""], ["`", "g"]], ["`", "\""]],
+ ["`", "replace"], ["RegExp", ["`", "\\\\n"], ["`", "g"]], ["`", "\n"]],
+ ["if", ["=", ["`", ":"], ["get", "token", 0]],
+ ["keyword", ["slice", "token", 1]],
+ ["if", ["=", ["`", "nil"], "token"],
+ null,
+ ["if", ["=", ["`", "true"], "token"],
+ true,
+ ["if", ["=", ["`", "false"], "token"],
+ false,
+ ["symbol", "token"]]]]]]]]]],
+
+["def", "read-list-entries", ["fn", ["rdr", "start", "end"],
+ ["let", ["tok", ["rdr-peek", "rdr"]],
+ ["if", "tok",
+ ["if", ["=", "end", "tok"],
+ ["`", []],
+ ["cons", ["read-form", "rdr"],
+ ["read-list-entries", "rdr", "start", "end"]]],
+ ["throw", ["str", ["`", "expected "], "end"]]]]]],
+
+["def", "read-list", ["fn", ["rdr", "start", "end"],
+ ["let", ["token", ["rdr-next", "rdr"]],
+ ["if", ["=", "start", "token"],
+ ["let", ["lst", ["read-list-entries", "rdr", "start", "end"]],
+ ["do",
+ ["rdr-next", "rdr"],
+ "lst"]],
+ ["throw", ["str", ["`", "expected "], "start"]]]]]],
+
+["def", "read-form", ["fn", ["rdr"],
+ ["let", ["token", ["rdr-peek", "rdr"]],
+ ["if", ["=", ["`", "'"], "token"],
+ ["do",
+ ["rdr-next", "rdr"],
+ ["list", ["symbol", ["`", "quote"]], ["read-form", "rdr"]]],
+ ["if", ["=", ["`", "`"], "token"],
+ ["do",
+ ["rdr-next", "rdr"],
+ ["list", ["symbol", ["`", "quasiquote"]], ["read-form", "rdr"]]],
+ ["if", ["=", ["`", "~"], "token"],
+ ["do",
+ ["rdr-next", "rdr"],
+ ["list", ["symbol", ["`", "unquote"]], ["read-form", "rdr"]]],
+ ["if", ["=", ["`", "~@"], "token"],
+ ["do",
+ ["rdr-next", "rdr"],
+ ["list", ["symbol", ["`", "splice-unquote"]], ["read-form", "rdr"]]],
+ ["if", ["=", ["`", "^"], "token"],
+ ["do",
+ ["rdr-next", "rdr"],
+ ["let", ["meta", ["read-form", "rdr"]],
+ ["list", ["symbol", ["`", "with-meta"]], ["read-form", "rdr"], "meta"]]],
+ ["if", ["=", ["`", "@"], "token"],
+ ["do",
+ ["rdr-next", "rdr"],
+ ["list", ["symbol", ["`", "deref"]], ["read-form", "rdr"]]],
+
+ ["if", ["=", ["`", ")"], "token"],
+ ["throw", ["`", "unexpected ')'"]],
+ ["if", ["=", ["`", "("], "token"],
+ ["read-list", "rdr", ["`", "("], ["`", ")"]],
+
+ ["if", ["=", ["`", "]"], "token"],
+ ["throw", ["`", "unexpected ']'"]],
+ ["if", ["=", ["`", "["], "token"],
+ ["vectorl", ["read-list", "rdr", ["`", "["], ["`", "]"]]],
+
+ ["if", ["=", ["`", "}"], "token"],
+ ["throw", ["`", "unexpected '}'"]],
+ ["if", ["=", ["`", "{"], "token"],
+ ["apply", "hash-map", ["read-list", "rdr", ["`", "{"], ["`", "}"]]],
+
+ ["read-atom", "rdr"]]]]]]]]]]]]]]]],
+
+["def", "read-str", ["fn", ["strn"],
+ ["let", ["tokens", ["tokenize", "strn"],
+ "rdr", ["rdr-new", "tokens"]],
+ ["if", ["empty?", "tokens"],
+ null,
+ ["read-form", "rdr"]]]]],
+
+null
+]
diff --git a/miniMAL/step0_repl.json b/miniMAL/step0_repl.json
new file mode 100644
index 0000000..6599930
--- /dev/null
+++ b/miniMAL/step0_repl.json
@@ -0,0 +1,21 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+
+["def", "READ", ["fn", ["strng"],
+ "strng"]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ "ast"]],
+
+["def", "PRINT", ["fn", ["exp"],
+ "exp"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["PRINT", ["EVAL", ["READ", "strng"], null]]]],
+
+["repl", ["`", "user> "], "rep"],
+
+null
+
+]
diff --git a/miniMAL/step1_read_print.json b/miniMAL/step1_read_print.json
new file mode 100644
index 0000000..dc1f269
--- /dev/null
+++ b/miniMAL/step1_read_print.json
@@ -0,0 +1,27 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+
+["def", "READ", ["fn", ["strng"],
+ ["read-str", "strng"]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ "ast"]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], null]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], "exc"]]]]],
+
+["repl", ["`", "user> "], "rep"],
+
+null
+
+]
diff --git a/miniMAL/step2_eval.json b/miniMAL/step2_eval.json
new file mode 100644
index 0000000..ee95fe2
--- /dev/null
+++ b/miniMAL/step2_eval.json
@@ -0,0 +1,60 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+
+["def", "READ", ["fn", ["strng"],
+ ["read-str", "strng"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["let", ["sym", ["get", "ast", ["`", "val"]]],
+ ["if", ["contains?", "env", "sym"],
+ ["get", "env", "sym"],
+ ["throw", ["str", ["`", "'"], "sym", ["`", "' not found"]]]]],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["apply", "f", "args"]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env",
+ ["hash-map",
+ ["`", "+"], "+",
+ ["`", "-"], "-",
+ ["`", "*"], "*",
+ ["`", "/"], ["fn", ["a", "b"], ["parseInt", ["/", "a", "b"]]]]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["repl", ["`", "user> "], "rep"],
+
+null
+
+]
diff --git a/miniMAL/step3_env.json b/miniMAL/step3_env.json
new file mode 100644
index 0000000..e72de07
--- /dev/null
+++ b/miniMAL/step3_env.json
@@ -0,0 +1,74 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+
+["def", "READ", ["fn", ["strng"],
+ ["read-str", "strng"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["apply", "f", "args"]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+["env-set", "repl-env", ["symbol", ["`", "+"]], "+"],
+["env-set", "repl-env", ["symbol", ["`", "-"]], "-"],
+["env-set", "repl-env", ["symbol", ["`", "*"]], "*"],
+["def", "div", ["fn", ["a", "b"], ["parseInt", ["/", "a", "b"]]]],
+["env-set", "repl-env", ["symbol", ["`", "/"]], "div"],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["repl", ["`", "user> "], "rep"],
+
+null
+
+]
diff --git a/miniMAL/step4_if_fn_do.json b/miniMAL/step4_if_fn_do.json
new file mode 100644
index 0000000..97e8021
--- /dev/null
+++ b/miniMAL/step4_if_fn_do.json
@@ -0,0 +1,92 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["let", ["el", ["eval-ast", ["rest", "ast"], "env"]],
+ ["nth", "el", ["-", ["count", "el"], 1]]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["apply", "f", "args"]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+
+["`", "core.mal: defined using mal itself"],
+["rep", ["`", "(def! not (fn* (a) (if a false true)))"]],
+
+["repl", ["`", "user> "], "rep"],
+
+null
+
+]
diff --git a/miniMAL/step5_tco.json b/miniMAL/step5_tco.json
new file mode 100644
index 0000000..06fd342
--- /dev/null
+++ b/miniMAL/step5_tco.json
@@ -0,0 +1,100 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["do",
+ ["eval-ast", ["slice", "ast", 1, ["-", ["count", "ast"], 1]], "env"],
+ ["EVAL", ["nth", "ast", ["-", ["count", "ast"], 1]], "env"]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["malfunc",
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["nth", "ast", 2], "env", ["nth", "ast", 1]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["if", ["malfunc?", "f"],
+ ["EVAL", ["get", "f", ["`", "ast"]],
+ ["env-new", ["get", "f", ["`", "env"]],
+ ["get", "f", ["`", "params"]],
+ "args"]],
+ ["apply", "f", "args"]]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+
+["`", "core.mal: defined using mal itself"],
+["rep", ["`", "(def! not (fn* (a) (if a false true)))"]],
+
+["repl", ["`", "user> "], "rep"],
+
+null
+
+]
diff --git a/miniMAL/step6_file.json b/miniMAL/step6_file.json
new file mode 100644
index 0000000..08f22c4
--- /dev/null
+++ b/miniMAL/step6_file.json
@@ -0,0 +1,107 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["do",
+ ["eval-ast", ["slice", "ast", 1, ["-", ["count", "ast"], 1]], "env"],
+ ["EVAL", ["nth", "ast", ["-", ["count", "ast"], 1]], "env"]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["malfunc",
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["nth", "ast", 2], "env", ["nth", "ast", 1]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["if", ["malfunc?", "f"],
+ ["EVAL", ["get", "f", ["`", "ast"]],
+ ["env-new", ["get", "f", ["`", "env"]],
+ ["get", "f", ["`", "params"]],
+ "args"]],
+ ["apply", "f", "args"]]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+["env-set", "repl-env", ["symbol", ["`", "eval"]],
+ ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]],
+["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]],
+ ["slice", "*ARGV*", 1]],
+
+["`", "core.mal: defined using mal itself"],
+["rep", ["`", "(def! not (fn* (a) (if a false true)))"]],
+["rep", ["`", "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"]],
+
+["if", ["not", ["empty?", "*ARGV*"]],
+ ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]],
+ ["repl", ["`", "user> "], "rep"]],
+
+null
+
+]
diff --git a/miniMAL/step7_quote.json b/miniMAL/step7_quote.json
new file mode 100644
index 0000000..78fd49e
--- /dev/null
+++ b/miniMAL/step7_quote.json
@@ -0,0 +1,131 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "pair?", ["fn", ["x"],
+ ["if", ["sequential?", "x"],
+ ["if", [">", ["count", "x"], 0], true, false],
+ false]]],
+
+["def", "quasiquote", ["fn", ["ast"],
+ ["if", ["not", ["pair?", "ast"]],
+ ["list", ["symbol", ["`", "quote"]], "ast"],
+ ["if", ["=", ["`", "unquote"], ["get", ["nth", "ast", 0], ["`", "val"]]],
+ ["nth", "ast", 1],
+ ["if", ["and", ["pair?", ["nth", "ast", 0]],
+ ["=", ["`", "splice-unquote"],
+ ["get", ["nth", ["nth", "ast", 0], 0], ["`", "val"]]]],
+ ["list", ["symbol", ["`", "concat"]],
+ ["nth", ["nth", "ast", 0], 1],
+ ["quasiquote", ["rest", "ast"]]],
+ ["list", ["symbol", ["`", "cons"]],
+ ["quasiquote", ["nth", "ast", 0]],
+ ["quasiquote", ["rest", "ast"]]]]]]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "quote"], "a0"],
+ ["nth", "ast", 1],
+ ["if", ["=", ["`", "quasiquote"], "a0"],
+ ["EVAL", ["quasiquote", ["nth", "ast", 1]], "env"],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["do",
+ ["eval-ast", ["slice", "ast", 1, ["-", ["count", "ast"], 1]], "env"],
+ ["EVAL", ["nth", "ast", ["-", ["count", "ast"], 1]], "env"]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["malfunc",
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["nth", "ast", 2], "env", ["nth", "ast", 1]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["if", ["malfunc?", "f"],
+ ["EVAL", ["get", "f", ["`", "ast"]],
+ ["env-new", ["get", "f", ["`", "env"]],
+ ["get", "f", ["`", "params"]],
+ "args"]],
+ ["apply", "f", "args"]]]]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+["env-set", "repl-env", ["symbol", ["`", "eval"]],
+ ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]],
+["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]],
+ ["slice", "*ARGV*", 1]],
+
+["`", "core.mal: defined using mal itself"],
+["rep", ["`", "(def! not (fn* (a) (if a false true)))"]],
+["rep", ["`", "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"]],
+
+["if", ["not", ["empty?", "*ARGV*"]],
+ ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]],
+ ["repl", ["`", "user> "], "rep"]],
+
+null
+
+]
diff --git a/miniMAL/step8_macros.json b/miniMAL/step8_macros.json
new file mode 100644
index 0000000..8b93fd0
--- /dev/null
+++ b/miniMAL/step8_macros.json
@@ -0,0 +1,157 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "pair?", ["fn", ["x"],
+ ["if", ["sequential?", "x"],
+ ["if", [">", ["count", "x"], 0], true, false],
+ false]]],
+
+["def", "quasiquote", ["fn", ["ast"],
+ ["if", ["not", ["pair?", "ast"]],
+ ["list", ["symbol", ["`", "quote"]], "ast"],
+ ["if", ["=", ["`", "unquote"], ["get", ["nth", "ast", 0], ["`", "val"]]],
+ ["nth", "ast", 1],
+ ["if", ["and", ["pair?", ["nth", "ast", 0]],
+ ["=", ["`", "splice-unquote"],
+ ["get", ["nth", ["nth", "ast", 0], 0], ["`", "val"]]]],
+ ["list", ["symbol", ["`", "concat"]],
+ ["nth", ["nth", "ast", 0], 1],
+ ["quasiquote", ["rest", "ast"]]],
+ ["list", ["symbol", ["`", "cons"]],
+ ["quasiquote", ["nth", "ast", 0]],
+ ["quasiquote", ["rest", "ast"]]]]]]]],
+
+["def", "macro?", ["fn", ["ast", "env"],
+ ["and", ["list?", "ast"],
+ ["symbol?", ["first", "ast"]],
+ ["not", ["=", null, ["env-find", "env", ["first", "ast"]]]],
+ ["let", ["fn", ["env-get", "env", ["first", "ast"]]],
+ ["and", ["malfunc?", "fn"],
+ ["get", "fn", ["`", "macro?"]]]]]]],
+
+["def", "macroexpand", ["fn", ["ast", "env"],
+ ["if", ["macro?", "ast", "env"],
+ ["let", ["mac", ["get", ["env-get", "env", ["first", "ast"]], ["`", "fn"]]],
+ ["macroexpand", ["apply", "mac", ["rest", "ast"]], "env"]],
+ "ast"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["ast", ["macroexpand", "ast", "env"]],
+ ["if", ["not", ["list?", "ast"]],
+ "ast",
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "quote"], "a0"],
+ ["nth", "ast", 1],
+ ["if", ["=", ["`", "quasiquote"], "a0"],
+ ["EVAL", ["quasiquote", ["nth", "ast", 1]], "env"],
+ ["if", ["=", ["`", "defmacro!"], "a0"],
+ ["let", ["func", ["EVAL", ["nth", "ast", 2], "env"]],
+ ["do",
+ ["set", "func", ["`", "macro?"], true],
+ ["env-set", "env", ["nth", "ast", 1], "func"]]],
+ ["if", ["=", ["`", "macroexpand"], "a0"],
+ ["macroexpand", ["nth", "ast", 1], "env"],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["do",
+ ["eval-ast", ["slice", "ast", 1, ["-", ["count", "ast"], 1]], "env"],
+ ["EVAL", ["nth", "ast", ["-", ["count", "ast"], 1]], "env"]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["malfunc",
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["nth", "ast", 2], "env", ["nth", "ast", 1]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["if", ["malfunc?", "f"],
+ ["EVAL", ["get", "f", ["`", "ast"]],
+ ["env-new", ["get", "f", ["`", "env"]],
+ ["get", "f", ["`", "params"]],
+ "args"]],
+ ["apply", "f", "args"]]]]]]]]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+["env-set", "repl-env", ["symbol", ["`", "eval"]],
+ ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]],
+["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]],
+ ["slice", "*ARGV*", 1]],
+
+["`", "core.mal: defined using mal 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", ["not", ["empty?", "*ARGV*"]],
+ ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]],
+ ["repl", ["`", "user> "], "rep"]],
+
+null
+
+]
diff --git a/miniMAL/step9_try.json b/miniMAL/step9_try.json
new file mode 100644
index 0000000..585a1b0
--- /dev/null
+++ b/miniMAL/step9_try.json
@@ -0,0 +1,168 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "pair?", ["fn", ["x"],
+ ["if", ["sequential?", "x"],
+ ["if", [">", ["count", "x"], 0], true, false],
+ false]]],
+
+["def", "quasiquote", ["fn", ["ast"],
+ ["if", ["not", ["pair?", "ast"]],
+ ["list", ["symbol", ["`", "quote"]], "ast"],
+ ["if", ["=", ["`", "unquote"], ["get", ["nth", "ast", 0], ["`", "val"]]],
+ ["nth", "ast", 1],
+ ["if", ["and", ["pair?", ["nth", "ast", 0]],
+ ["=", ["`", "splice-unquote"],
+ ["get", ["nth", ["nth", "ast", 0], 0], ["`", "val"]]]],
+ ["list", ["symbol", ["`", "concat"]],
+ ["nth", ["nth", "ast", 0], 1],
+ ["quasiquote", ["rest", "ast"]]],
+ ["list", ["symbol", ["`", "cons"]],
+ ["quasiquote", ["nth", "ast", 0]],
+ ["quasiquote", ["rest", "ast"]]]]]]]],
+
+["def", "macro?", ["fn", ["ast", "env"],
+ ["and", ["list?", "ast"],
+ ["symbol?", ["first", "ast"]],
+ ["not", ["=", null, ["env-find", "env", ["first", "ast"]]]],
+ ["let", ["fn", ["env-get", "env", ["first", "ast"]]],
+ ["and", ["malfunc?", "fn"],
+ ["get", "fn", ["`", "macro?"]]]]]]],
+
+["def", "macroexpand", ["fn", ["ast", "env"],
+ ["if", ["macro?", "ast", "env"],
+ ["let", ["mac", ["get", ["env-get", "env", ["first", "ast"]], ["`", "fn"]]],
+ ["macroexpand", ["apply", "mac", ["rest", "ast"]], "env"]],
+ "ast"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["ast", ["macroexpand", "ast", "env"]],
+ ["if", ["not", ["list?", "ast"]],
+ "ast",
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "quote"], "a0"],
+ ["nth", "ast", 1],
+ ["if", ["=", ["`", "quasiquote"], "a0"],
+ ["EVAL", ["quasiquote", ["nth", "ast", 1]], "env"],
+ ["if", ["=", ["`", "defmacro!"], "a0"],
+ ["let", ["func", ["EVAL", ["nth", "ast", 2], "env"]],
+ ["do",
+ ["set", "func", ["`", "macro?"], true],
+ ["env-set", "env", ["nth", "ast", 1], "func"]]],
+ ["if", ["=", ["`", "macroexpand"], "a0"],
+ ["macroexpand", ["nth", "ast", 1], "env"],
+ ["if", ["=", ["`", "try*"], "a0"],
+ ["if", ["=", ["`", "catch*"],
+ ["get", ["nth", ["nth", "ast", 2], 0], ["`", "val"]]],
+ ["try",
+ ["EVAL", ["nth", "ast", 1], "env"],
+ ["catch", "exc",
+ ["EVAL", ["nth", ["nth", "ast", 2], 2],
+ ["env-new", "env",
+ ["list", ["nth", ["nth", "ast", 2], 1]],
+ ["list", "exc"]]]]],
+ ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["do",
+ ["eval-ast", ["slice", "ast", 1, ["-", ["count", "ast"], 1]], "env"],
+ ["EVAL", ["nth", "ast", ["-", ["count", "ast"], 1]], "env"]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["malfunc",
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["nth", "ast", 2], "env", ["nth", "ast", 1]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["if", ["malfunc?", "f"],
+ ["EVAL", ["get", "f", ["`", "ast"]],
+ ["env-new", ["get", "f", ["`", "env"]],
+ ["get", "f", ["`", "params"]],
+ "args"]],
+ ["apply", "f", "args"]]]]]]]]]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+["env-set", "repl-env", ["symbol", ["`", "eval"]],
+ ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]],
+["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]],
+ ["slice", "*ARGV*", 1]],
+
+["`", "core.mal: defined using mal 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", ["not", ["empty?", "*ARGV*"]],
+ ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]],
+ ["repl", ["`", "user> "], "rep"]],
+
+null
+
+]
diff --git a/miniMAL/stepA_mal.json b/miniMAL/stepA_mal.json
new file mode 100644
index 0000000..2181d30
--- /dev/null
+++ b/miniMAL/stepA_mal.json
@@ -0,0 +1,171 @@
+["do",
+
+["load-file", ["`", "miniMAL-core.json"]],
+["load-file", ["`", "types.json"]],
+["load-file", ["`", "reader.json"]],
+["load-file", ["`", "printer.json"]],
+["load-file", ["`", "env.json"]],
+["load-file", ["`", "core.json"]],
+
+["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]],
+
+["def", "pair?", ["fn", ["x"],
+ ["if", ["sequential?", "x"],
+ ["if", [">", ["count", "x"], 0], true, false],
+ false]]],
+
+["def", "quasiquote", ["fn", ["ast"],
+ ["if", ["not", ["pair?", "ast"]],
+ ["list", ["symbol", ["`", "quote"]], "ast"],
+ ["if", ["=", ["`", "unquote"], ["get", ["nth", "ast", 0], ["`", "val"]]],
+ ["nth", "ast", 1],
+ ["if", ["and", ["pair?", ["nth", "ast", 0]],
+ ["=", ["`", "splice-unquote"],
+ ["get", ["nth", ["nth", "ast", 0], 0], ["`", "val"]]]],
+ ["list", ["symbol", ["`", "concat"]],
+ ["nth", ["nth", "ast", 0], 1],
+ ["quasiquote", ["rest", "ast"]]],
+ ["list", ["symbol", ["`", "cons"]],
+ ["quasiquote", ["nth", "ast", 0]],
+ ["quasiquote", ["rest", "ast"]]]]]]]],
+
+["def", "macro?", ["fn", ["ast", "env"],
+ ["and", ["list?", "ast"],
+ ["symbol?", ["first", "ast"]],
+ ["not", ["=", null, ["env-find", "env", ["first", "ast"]]]],
+ ["let", ["fn", ["env-get", "env", ["first", "ast"]]],
+ ["and", ["malfunc?", "fn"],
+ ["get", "fn", ["`", "macro?"]]]]]]],
+
+["def", "macroexpand", ["fn", ["ast", "env"],
+ ["if", ["macro?", "ast", "env"],
+ ["let", ["mac", ["get", ["env-get", "env", ["first", "ast"]], ["`", "fn"]]],
+ ["macroexpand", ["apply", "mac", ["rest", "ast"]], "env"]],
+ "ast"]]],
+
+["def", "eval-ast", ["fn", ["ast", "env"],
+ ["if", ["symbol?", "ast"],
+ ["env-get", "env", "ast"],
+ ["if", ["list?", "ast"],
+ ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"],
+ ["if", ["vector?", "ast"],
+ ["vectorl", ["map", ["fn", ["x"], ["EVAL", "x", "env"]], "ast"]],
+ ["if", ["map?", "ast"],
+ ["let", ["new-hm", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"], ["set", "new-hm",
+ ["EVAL", "k", "env"],
+ ["EVAL", ["get", "ast", "k"], "env"]]],
+ ["keys", "ast"]],
+ "new-hm"]],
+ "ast"]]]]]],
+
+["def", "LET", ["fn", ["env", "args"],
+ ["if", [">", ["count", "args"], 0],
+ ["do",
+ ["env-set", "env", ["nth", "args", 0],
+ ["EVAL", ["nth", "args", 1], "env"]],
+ ["LET", "env", ["rest", ["rest", "args"]]]]]]],
+
+["def", "EVAL", ["fn", ["ast", "env"],
+ ["if", ["not", ["list?", "ast"]],
+ ["eval-ast", "ast", "env"],
+ ["let", ["ast", ["macroexpand", "ast", "env"]],
+ ["if", ["not", ["list?", "ast"]],
+ "ast",
+ ["let", ["a0", ["get", ["first", "ast"], ["`", "val"]]],
+ ["if", ["=", ["`", "def!"], "a0"],
+ ["env-set", "env", ["nth", "ast", 1],
+ ["EVAL", ["nth", "ast", 2], "env"]],
+ ["if", ["=", ["`", "let*"], "a0"],
+ ["let", ["let-env", ["env-new", "env"]],
+ ["do",
+ ["LET", "let-env", ["nth", "ast", 1]],
+ ["EVAL", ["nth", "ast", 2], "let-env"]]],
+ ["if", ["=", ["`", "quote"], "a0"],
+ ["nth", "ast", 1],
+ ["if", ["=", ["`", "quasiquote"], "a0"],
+ ["EVAL", ["quasiquote", ["nth", "ast", 1]], "env"],
+ ["if", ["=", ["`", "defmacro!"], "a0"],
+ ["let", ["func", ["EVAL", ["nth", "ast", 2], "env"]],
+ ["do",
+ ["set", "func", ["`", "macro?"], true],
+ ["env-set", "env", ["nth", "ast", 1], "func"]]],
+ ["if", ["=", ["`", "macroexpand"], "a0"],
+ ["macroexpand", ["nth", "ast", 1], "env"],
+ ["if", ["=", ["`", "try*"], "a0"],
+ ["if", ["=", ["`", "catch*"],
+ ["get", ["nth", ["nth", "ast", 2], 0], ["`", "val"]]],
+ ["try",
+ ["EVAL", ["nth", "ast", 1], "env"],
+ ["catch", "exc",
+ ["EVAL", ["nth", ["nth", "ast", 2], 2],
+ ["env-new", "env",
+ ["list", ["nth", ["nth", "ast", 2], 1]],
+ ["list", "exc"]]]]],
+ ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["=", ["`", "do"], "a0"],
+ ["do",
+ ["eval-ast", ["slice", "ast", 1, ["-", ["count", "ast"], 1]], "env"],
+ ["EVAL", ["nth", "ast", ["-", ["count", "ast"], 1]], "env"]],
+ ["if", ["=", ["`", "if"], "a0"],
+ ["let", ["cond", ["EVAL", ["nth", "ast", 1], "env"]],
+ ["if", ["or", ["=", "cond", null], ["=", "cond", false]],
+ ["if", [">", ["count", "ast"], 3],
+ ["EVAL", ["nth", "ast", 3], "env"],
+ null],
+ ["EVAL", ["nth", "ast", 2], "env"]]],
+ ["if", ["=", ["`", "fn*"], "a0"],
+ ["malfunc",
+ ["fn", ["&", "args"],
+ ["let", ["e", ["env-new", "env", ["nth", "ast", 1], "args"]],
+ ["EVAL", ["nth", "ast", 2], "e"]]],
+ ["nth", "ast", 2], "env", ["nth", "ast", 1]],
+ ["let", ["el", ["eval-ast", "ast", "env"],
+ "f", ["first", "el"],
+ "args", ["rest", "el"]],
+ ["if", ["malfunc?", "f"],
+ ["EVAL", ["get", "f", ["`", "ast"]],
+ ["env-new", ["get", "f", ["`", "env"]],
+ ["get", "f", ["`", "params"]],
+ "args"]],
+ ["apply", "f", "args"]]]]]]]]]]]]]]]]]]],
+
+["def", "PRINT", ["fn", ["exp"],
+ ["pr-str", "exp", true]]],
+
+
+["def", "repl-env", ["env-new"]],
+
+["def", "rep", ["fn", ["strng"],
+ ["try",
+ ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]],
+ ["catch", "exc",
+ ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]],
+
+["`", "core.mal: defined using miniMAL"],
+["map", ["fn", ["k"], ["env-set", "repl-env",
+ ["symbol", "k"],
+ ["get", "core-ns", "k"]]],
+ ["keys", "core-ns"]],
+["env-set", "repl-env", ["symbol", ["`", "eval"]],
+ ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]],
+["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]],
+ ["slice", "*ARGV*", 1]],
+
+["`", "core.mal: defined using mal itself"],
+["rep", ["`", "(def! *host-language* \"miniMAL\")"]],
+["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", ["not", ["empty?", "*ARGV*"]],
+ ["println", ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]]],
+ ["do",
+ ["rep", ["`", "(println (str \"Mal [\" *host-language* \"]\"))"]],
+ ["repl", ["`", "user> "], "rep"]]],
+
+null
+
+]
diff --git a/miniMAL/types.json b/miniMAL/types.json
new file mode 100644
index 0000000..372dff9
--- /dev/null
+++ b/miniMAL/types.json
@@ -0,0 +1,145 @@
+["do",
+
+["`", "Utility Functions"],
+["def", "_cmp_seqs", ["fn", ["a", "b"],
+ ["if", ["not", ["=", ["count", "a"], ["count", "b"]]],
+ false,
+ ["if", ["empty?", "a"],
+ true,
+ ["if", ["equal?", ["get", "a", 0], ["get", "b", 0]],
+ ["_cmp_seqs", ["rest", "a"], ["rest", "b"]],
+ false]]]]],
+
+["def", "equal?", ["fn", ["a", "b"],
+ ["if", ["sequential?", "a"],
+ ["if", ["sequential?", "b"],
+ ["_cmp_seqs", "a", "b"],
+ false],
+ ["if", ["symbol?", "a"],
+ ["=", ["get", "a", ["`", "val"]], ["get", "b", ["`", "val"]]],
+ ["=", "a", "b"]]]]],
+
+["def", "_clone", ["fn", ["obj"],
+ ["if", ["list?", "obj"],
+ ["slice", "obj", 0],
+ ["if", ["vector?", "obj"],
+ ["let", ["new-obj", ["slice", "obj", 0]],
+ ["do",
+ ["set", "new-obj", ["`", "__vector?__"], true],
+ "new-obj"]],
+ ["if", ["map?", "obj"],
+ ["let", ["new-obj", ["hash-map"]],
+ ["do",
+ ["map", ["fn", ["k"],
+ ["if", [".", "obj", ["`", "hasOwnProperty"], "k"],
+ ["set", "new-obj", "k", ["get", "obj", "k"]],
+ null]],
+ ["keys", "obj"]],
+ "new-obj"]],
+ ["if", ["malfunc?", "obj"],
+ ["let", ["new-obj", ["malfunc", ["get", "obj", ["`", "fn"]],
+ ["get", "obj", ["`", "ast"]],
+ ["get", "obj", ["`", "env"]],
+ ["get", "obj", ["`", "params"]]]],
+ ["do",
+ ["set", "new-obj", ["`", "macro?"], ["get", "obj", ["`", "macro?"]]],
+ ["set", "new-obj", ["`", "__meta__"], ["get", "obj", ["`", "__meta__"]]],
+ "new-obj"]],
+ ["throw", "clone of unsupported type"]]]]]]],
+
+["def", "clone", ["fn", ["obj"],
+ ["let", ["new-obj", ["_clone", "obj"]],
+ ["do",
+ [".", "Object", ["`", "defineProperty"], "new-obj", ["`", "__meta__"],
+ {"enumerable": false, "writable": true}],
+ "new-obj"]]]],
+
+["def", "assoc!", ["fn", ["a", "b", "c"], ["do", ["set", "a", "b", "c"], "a"]]],
+["def", "assocs!", ["fn", ["hm", "kvs"],
+ ["if", ["empty?", "kvs"],
+ "hm",
+ ["do",
+ ["assoc!", "hm", ["get", "kvs", 0], ["get", "kvs", 1]],
+ ["assocs!", "hm", ["slice", "kvs", 2]]]]]],
+
+
+["def", "Symbol", ["fn", [], null]],
+["def", "symbol", ["fn", ["name"],
+ ["assoc!", ["new", "Symbol"], ["`", "val"], "name"]]],
+
+["def", "symbol?", ["fn", ["a"],
+ ["isa", "a", "Symbol"]]],
+
+
+["def", "keyword", ["fn", ["name"],
+ ["str", ["`", "\u029e"], "name"]]],
+
+["def", "keyword?", ["fn", ["kw"],
+ ["and", ["=", ["`", "[object String]"], ["classOf", "kw"]],
+ ["=", ["`", "\u029e"], ["get", "kw", 0]]]]],
+
+
+["`", "Override some list defs to account for Vectors"],
+["def", "sequential?", ["fn", ["a"],
+ [".", "Array", ["`", "isArray"], "a"]]],
+
+["def", "list?", ["fn", ["a"],
+ ["if", [".", "Array", ["`", "isArray"], "a"],
+ ["if", [".-", "a", ["`", "__vector?__"]],
+ false,
+ true],
+ false]]],
+
+["def", "empty?", ["fn", ["a"],
+ ["if", ["sequential?", "a"],
+ ["if", ["=", 0, [".-", "a", ["`", "length"]]],
+ true,
+ false],
+ ["=", "a", null]]]],
+
+
+["def", "vectorl", ["fn", ["lst"],
+ ["let", ["vec", ["slice", "lst", 0]],
+ ["do",
+ ["set", "vec", ["`", "__vector?__"], true],
+ "vec"]]]],
+
+["def", "vector", ["fn", ["&", "args"], ["vectorl", "args"]]],
+
+["def", "vector?", ["fn", ["a"],
+ ["if", [".", "Array", ["`", "isArray"], "a"],
+ ["if", [".-", "a", ["`", "__vector?__"]],
+ true,
+ false],
+ false]]],
+
+
+["def", "HashMap", ["fn", [], null]],
+["def", "hash-map", ["fn", ["&", "a"],
+ ["assocs!", ["new", "HashMap"], "a"]]],
+["def", "map?", ["fn", ["a"],
+ ["isa", "a", "HashMap"]]],
+
+["def", "MalFunc", ["fn", [], null]],
+["def", "malfunc", ["fn", ["fn", "ast", "env", "params"],
+ ["assocs!", ["new", "MalFunc"],
+ ["list", ["`", "fn"], "fn",
+ ["`", "ast"], "ast",
+ ["`", "env"], "env",
+ ["`", "params"], "params",
+ ["`", "macro?"], false]]]],
+
+["def", "malfunc?", ["fn", ["a"],
+ ["isa", "a", "MalFunc"]]],
+
+["def", "Atom", ["fn", [], null]],
+["def", "atom", ["fn", ["a"],
+ ["let", ["atm", ["new", "Atom"]],
+ ["do",
+ ["set", "atm", ["`", "val"], "a"],
+ "atm"]]]],
+["def", "atom?", ["fn", ["a"],
+ ["isa", "a", "Atom"]]],
+
+null
+]
diff --git a/ocaml/Makefile b/ocaml/Makefile
new file mode 100644
index 0000000..9245dd5
--- /dev/null
+++ b/ocaml/Makefile
@@ -0,0 +1,37 @@
+STEPS = step0_repl.ml step1_read_print.ml step2_eval.ml step3_env.ml \
+ step4_if_fn_do.ml step5_tco.ml step6_file.ml step7_quote.ml \
+ step8_macros.ml step9_try.ml stepA_mal.ml
+MODULES = types.ml reader.ml printer.ml env.ml core.ml
+LIBS = str.cmxa unix.cmxa
+MAL_LIB = mal_lib.cmxa
+
+STEP_BINS = $(STEPS:%.ml=%)
+LAST_STEP_BIN = $(word $(words $(STEP_BINS)),$(STEP_BINS))
+
+all: $(STEP_BINS) mal
+
+mal: $(LAST_STEP_BIN)
+ cp $< $@
+
+# ocaml repl apparently needs bytecode, not native, compilation.
+# Just do it all right here:
+repl:
+ ocamlc -c $(LIBS:%.cmxa=%.cma) $(MODULES) $(STEPS)
+ rlwrap ocaml $(LIBS:%.cmxa=%.cma) $(MODULES:%.ml=%.cmo)
+
+$(MAL_LIB): $(MODULES)
+ ocamlopt -a $(MODULES) -o $@
+
+$(STEP_BINS): %: %.ml $(MAL_LIB)
+ ocamlopt $(LIBS) $(MAL_LIB) $< -o $@
+
+clean:
+ rm -f $(STEP_BINS) mal mal_lib.* *.cmo *.cmx *.cmi *.o
+
+stats: $(MODULES) stepA_mal.ml
+ @wc $^
+
+stats-lisp: env.ml core.ml stepA_mal.ml
+ @wc $^
+
+.PHONY: all repl clean stats stats-lisp
diff --git a/ocaml/core.ml b/ocaml/core.ml
new file mode 100644
index 0000000..12bf3c3
--- /dev/null
+++ b/ocaml/core.ml
@@ -0,0 +1,206 @@
+module T = Types.Types
+let ns = Env.make None
+
+let num_fun t f = Types.fn
+ (function
+ | [(T.Int a); (T.Int b)] -> t (f a b)
+ | _ -> raise (Invalid_argument "Numeric args required for this Mal builtin"))
+
+let mk_int x = T.Int x
+let mk_bool x = T.Bool x
+
+let seq = function
+ | T.List { T.value = xs } -> xs
+ | T.Vector { T.value = xs } -> xs
+ | T.Map { T.value = xs } ->
+ Types.MalMap.fold (fun k v list -> k :: v :: list) xs []
+ | _ -> []
+
+let rec assoc = function
+ | c :: k :: v :: (_ :: _ as xs) -> assoc ((assoc [c; k; v]) :: xs)
+ | [T.Nil; k; v] -> Types.map (Types.MalMap.add k v Types.MalMap.empty)
+ | [T.Map { T.value = m; T.meta = meta }; k; v]
+ -> T.Map { T.value = (Types.MalMap.add k v m);
+ T.meta = meta }
+ | _ -> T.Nil
+
+let rec dissoc = function
+ | c :: x :: (_ :: _ as xs) -> dissoc ((dissoc [c; x]) :: xs)
+ | [T.Map { T.value = m; T.meta = meta }; k]
+ -> T.Map { T.value = (Types.MalMap.remove k m);
+ T.meta = meta }
+ | _ -> T.Nil
+
+let rec conj = function
+ | c :: x :: (_ :: _ as xs) -> conj ((conj [c; x]) :: xs)
+ | [T.Map { T.value = c; T.meta = meta }; T.Vector { T.value = [k; v] }]
+ -> T.Map { T.value = (Types.MalMap.add k v c);
+ T.meta = meta }
+ | [T.List { T.value = c; T.meta = meta }; x ]
+ -> T.List { T.value = x :: c;
+ T.meta = meta }
+ | [T.Vector { T.value = c; T.meta = meta }; x ]
+ -> T.Vector { T.value = c @ [x];
+ T.meta = meta }
+ | _ -> T.Nil
+
+let init env = begin
+ Env.set env (Types.symbol "throw")
+ (Types.fn (function [ast] -> raise (Types.MalExn ast) | _ -> T.Nil));
+
+ Env.set env (Types.symbol "+") (num_fun mk_int ( + ));
+ Env.set env (Types.symbol "-") (num_fun mk_int ( - ));
+ Env.set env (Types.symbol "*") (num_fun mk_int ( * ));
+ Env.set env (Types.symbol "/") (num_fun mk_int ( / ));
+ Env.set env (Types.symbol "<") (num_fun mk_bool ( < ));
+ Env.set env (Types.symbol "<=") (num_fun mk_bool ( <= ));
+ Env.set env (Types.symbol ">") (num_fun mk_bool ( > ));
+ Env.set env (Types.symbol ">=") (num_fun mk_bool ( >= ));
+
+ Env.set env (Types.symbol "list") (Types.fn (function xs -> Types.list xs));
+ Env.set env (Types.symbol "list?")
+ (Types.fn (function [T.List _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "vector") (Types.fn (function xs -> Types.vector xs));
+ Env.set env (Types.symbol "vector?")
+ (Types.fn (function [T.Vector _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "empty?")
+ (Types.fn (function
+ | [T.List {T.value = []}] -> T.Bool true
+ | [T.Vector {T.value = []}] -> T.Bool true
+ | _ -> T.Bool false));
+ Env.set env (Types.symbol "count")
+ (Types.fn (function
+ | [T.List {T.value = xs}]
+ | [T.Vector {T.value = xs}] -> T.Int (List.length xs)
+ | _ -> T.Int 0));
+ Env.set env (Types.symbol "=")
+ (Types.fn (function
+ | [T.List a; T.Vector b] -> T.Bool (a = b)
+ | [T.Vector a; T.List b] -> T.Bool (a = b)
+ | [a; b] -> T.Bool (a = b)
+ | _ -> T.Bool false));
+
+ Env.set env (Types.symbol "pr-str")
+ (Types.fn (function xs ->
+ T.String (String.concat " " (List.map (fun s -> Printer.pr_str s true) xs))));
+ Env.set env (Types.symbol "str")
+ (Types.fn (function xs ->
+ T.String (String.concat "" (List.map (fun s -> Printer.pr_str s false) xs))));
+ Env.set env (Types.symbol "prn")
+ (Types.fn (function xs ->
+ print_endline (String.concat " " (List.map (fun s -> Printer.pr_str s true) xs));
+ T.Nil));
+ Env.set env (Types.symbol "println")
+ (Types.fn (function xs ->
+ print_endline (String.concat " " (List.map (fun s -> Printer.pr_str s false) xs));
+ T.Nil));
+
+ Env.set env (Types.symbol "compare")
+ (Types.fn (function [a; b] -> T.Int (compare a b) | _ -> T.Nil));
+ Env.set env (Types.symbol "with-meta")
+ (Types.fn (function [a; b] -> Reader.with_meta a b | _ -> T.Nil));
+ Env.set env (Types.symbol "meta")
+ (Types.fn (function [x] -> Printer.meta x | _ -> T.Nil));
+
+ Env.set env (Types.symbol "read-string")
+ (Types.fn (function [T.String x] -> Reader.read_str x | _ -> T.Nil));
+ Env.set env (Types.symbol "slurp")
+ (Types.fn (function [T.String x] -> T.String (Reader.slurp x) | _ -> T.Nil));
+
+ Env.set env (Types.symbol "cons")
+ (Types.fn (function [x; xs] -> Types.list (x :: (seq xs)) | _ -> T.Nil));
+ Env.set env (Types.symbol "concat")
+ (Types.fn (let rec concat =
+ function
+ | x :: y :: more -> concat ((Types.list ((seq x) @ (seq y))) :: more)
+ | [x] -> x
+ | [] -> Types.list []
+ in concat));
+
+ Env.set env (Types.symbol "nth")
+ (Types.fn (function [xs; T.Int i] -> List.nth (seq xs) i | _ -> T.Nil));
+ Env.set env (Types.symbol "first")
+ (Types.fn (function
+ | [xs] -> (match seq xs with x :: _ -> x | _ -> T.Nil)
+ | _ -> T.Nil));
+ Env.set env (Types.symbol "rest")
+ (Types.fn (function
+ | [xs] -> Types.list (match seq xs with _ :: xs -> xs | _ -> [])
+ | _ -> T.Nil));
+
+ Env.set env (Types.symbol "symbol")
+ (Types.fn (function [T.String x] -> Types.symbol x | _ -> T.Nil));
+ Env.set env (Types.symbol "symbol?")
+ (Types.fn (function [T.Symbol _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "keyword")
+ (Types.fn (function [T.String x] -> T.Keyword x | _ -> T.Nil));
+ Env.set env (Types.symbol "keyword?")
+ (Types.fn (function [T.Keyword _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "nil?")
+ (Types.fn (function [T.Nil] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "true?")
+ (Types.fn (function [T.Bool true] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "false?")
+ (Types.fn (function [T.Bool false] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "sequential?")
+ (Types.fn (function [T.List _] | [T.Vector _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "apply")
+ (Types.fn (function
+ | (T.Fn { T.value = f } :: apply_args) ->
+ (match List.rev apply_args with
+ | last_arg :: rev_args ->
+ f ((List.rev rev_args) @ (seq last_arg))
+ | [] -> f [])
+ | _ -> raise (Invalid_argument "First arg to apply must be a fn")));
+ Env.set env (Types.symbol "map")
+ (Types.fn (function
+ | [T.Fn { T.value = f }; xs] ->
+ Types.list (List.map (fun x -> f [x]) (seq xs))
+ | _ -> T.Nil));
+ Env.set env (Types.symbol "readline")
+ (Types.fn (function
+ | [T.String x] -> print_string x; T.String (read_line ())
+ | _ -> T.String (read_line ())));
+
+ Env.set env (Types.symbol "map?")
+ (Types.fn (function [T.Map _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "hash-map")
+ (Types.fn (function xs -> Types.list_into_map Types.MalMap.empty xs));
+ Env.set env (Types.symbol "assoc") (Types.fn assoc);
+ Env.set env (Types.symbol "dissoc") (Types.fn dissoc);
+ Env.set env (Types.symbol "get")
+ (Types.fn (function
+ | [T.Map { T.value = m }; k]
+ -> (try Types.MalMap.find k m with _ -> T.Nil)
+ | _ -> T.Nil));
+ Env.set env (Types.symbol "keys")
+ (Types.fn (function
+ | [T.Map { T.value = m }]
+ -> Types.list (Types.MalMap.fold (fun k _ c -> k :: c) m [])
+ | _ -> T.Nil));
+ Env.set env (Types.symbol "vals")
+ (Types.fn (function
+ | [T.Map { T.value = m }]
+ -> Types.list (Types.MalMap.fold (fun _ v c -> v :: c) m [])
+ | _ -> T.Nil));
+ Env.set env (Types.symbol "contains?")
+ (Types.fn (function
+ | [T.Map { T.value = m }; k] -> T.Bool (Types.MalMap.mem k m)
+ | _ -> T.Bool false));
+ Env.set env (Types.symbol "conj") (Types.fn conj);
+
+ Env.set env (Types.symbol "atom?")
+ (Types.fn (function [T.Atom _] -> T.Bool true | _ -> T.Bool false));
+ Env.set env (Types.symbol "atom")
+ (Types.fn (function [x] -> T.Atom (ref x) | _ -> T.Nil));
+ Env.set env (Types.symbol "deref")
+ (Types.fn (function [T.Atom x] -> !x | _ -> T.Nil));
+ Env.set env (Types.symbol "reset!")
+ (Types.fn (function [T.Atom x; v] -> x := v; v | _ -> T.Nil));
+ Env.set env (Types.symbol "swap!")
+ (Types.fn (function T.Atom x :: T.Fn { T.value = f } :: args
+ -> let v = f (!x :: args) in x := v; v | _ -> T.Nil));
+
+ Env.set env (Types.symbol "time-ms")
+ (Types.fn (function _ -> T.Int (truncate (1000.0 *. Unix.gettimeofday ()))));
+end
diff --git a/ocaml/env.ml b/ocaml/env.ml
new file mode 100644
index 0000000..cb32360
--- /dev/null
+++ b/ocaml/env.ml
@@ -0,0 +1,33 @@
+module T = Types.Types
+module Data = Map.Make (String)
+
+type env = {
+ outer : env option;
+ data : Types.mal_type Data.t ref;
+}
+
+let make outer = { outer = outer; data = ref Data.empty }
+
+let set env sym value =
+ match sym with
+ | T.Symbol { T.value = key } -> env.data := Data.add key value !(env.data)
+ | _ -> raise (Invalid_argument "set requires a Symbol for its key")
+
+let rec find env sym =
+ match sym with
+ | T.Symbol { T.value = key } ->
+ (if Data.mem key !(env.data) then
+ Some env
+ else
+ match env.outer with
+ | Some outer -> find outer sym
+ | None -> None)
+ | _ -> raise (Invalid_argument "find requires a Symbol for its key")
+
+let get env sym =
+ match sym with
+ | T.Symbol { T.value = key } ->
+ (match find env sym with
+ | Some found_env -> Data.find key !(found_env.data)
+ | None -> raise (Invalid_argument ("'" ^ key ^ "' not found")))
+ | _ -> raise (Invalid_argument "get requires a Symbol for its key")
diff --git a/ocaml/printer.ml b/ocaml/printer.ml
new file mode 100644
index 0000000..135c3ce
--- /dev/null
+++ b/ocaml/printer.ml
@@ -0,0 +1,38 @@
+module T = Types.Types
+
+let meta obj =
+ match obj with
+ | T.List { T.meta = meta } -> meta
+ | T.Map { T.meta = meta } -> meta
+ | T.Vector { T.meta = meta } -> meta
+ | T.Symbol { T.meta = meta } -> meta
+ | T.Fn { T.meta = meta } -> meta
+ | _ -> T.Nil
+
+let rec pr_str mal_obj print_readably =
+ let r = print_readably in
+ match mal_obj with
+ | T.Int i -> string_of_int i
+ | T.Symbol { T.value = s } -> s
+ | T.Keyword s -> ":" ^ s
+ | T.Nil -> "nil"
+ | T.Bool true -> "true"
+ | T.Bool false -> "false"
+ | T.String s ->
+ if r
+ then "\"" ^ (Reader.gsub (Str.regexp "\\([\"\\\n]\\)")
+ (function
+ | "\n" -> "\\n"
+ | x -> "\\" ^ x)
+ s) ^ "\""
+ else s
+ | T.List { T.value = xs } ->
+ "(" ^ (String.concat " " (List.map (fun s -> pr_str s r) xs)) ^ ")"
+ | T.Vector { T.value = xs } ->
+ "[" ^ (String.concat " " (List.map (fun s -> pr_str s r) xs)) ^ "]"
+ | T.Map { T.value = xs } ->
+ "{" ^ (Types.MalMap.fold (fun k v s -> s ^ (if s = "" then "" else ", ") ^ (pr_str k r)
+ ^ " " ^ (pr_str v r)) xs "")
+ ^ "}"
+ | T.Fn f -> "#<fn>"
+ | T.Atom x -> "(atom " ^ (pr_str !x r) ^ ")"
diff --git a/ocaml/reader.ml b/ocaml/reader.ml
new file mode 100644
index 0000000..7456cf8
--- /dev/null
+++ b/ocaml/reader.ml
@@ -0,0 +1,111 @@
+module T = Types.Types
+ (* ^file ^module *)
+
+let slurp filename =
+ let chan = open_in filename in
+ let b = Buffer.create 27 in
+ Buffer.add_channel b chan (in_channel_length chan) ;
+ close_in chan ;
+ Buffer.contents b
+
+let find_re re str =
+ List.map (function | Str.Delim x -> x | Str.Text x -> "impossible!")
+ (List.filter (function | Str.Delim x -> true | Str.Text x -> false)
+ (Str.full_split re str))
+
+let gsub re f str =
+ String.concat
+ "" (List.map (function | Str.Delim x -> f x | Str.Text x -> x)
+ (Str.full_split re str))
+
+let token_re = (Str.regexp "~@\\|[][{}()'`~^@]\\|\"\\(\\\\.\\|[^\"]\\)*\"\\|;.*\\|[^][ \n{}('\"`,;)]*")
+
+type reader = {
+ form : Types.mal_type;
+ tokens : string list;
+}
+
+type list_reader = {
+ list_form : Types.mal_type list;
+ tokens : string list;
+}
+
+let read_atom token =
+ match token with
+ | "nil" -> T.Nil
+ | "true" -> T.Bool true
+ | "false" -> T.Bool false
+ | _ ->
+ match token.[0] with
+ | '0'..'9' -> T.Int (int_of_string token)
+ | '"' -> T.String (gsub (Str.regexp "\\\\.")
+ (function
+ | "\\n" -> "\n"
+ | x -> String.sub x 1 1)
+ (String.sub token 1 ((String.length token) - 2)))
+ | ':' -> T.Keyword (Str.replace_first (Str.regexp "^:") "" token)
+ | _ -> Types.symbol token
+
+let with_meta obj meta =
+ match obj with
+ | T.List { T.value = v }
+ -> T.List { T.value = v; T.meta = meta }; | T.Map { T.value = v }
+ -> T.Map { T.value = v; T.meta = meta }; | T.Vector { T.value = v }
+ -> T.Vector { T.value = v; T.meta = meta }; | T.Symbol { T.value = v }
+ -> T.Symbol { T.value = v; T.meta = meta }; | T.Fn { T.value = v }
+ -> T.Fn { T.value = v; T.meta = meta };
+ | _ -> raise (Invalid_argument "metadata not supported on this type")
+
+let rec read_list eol list_reader =
+ match list_reader.tokens with
+ | [] -> output_string stderr ("expected '" ^ eol ^ "', got EOF\n");
+ flush stderr;
+ raise End_of_file;
+ | token :: tokens ->
+ if Str.string_match (Str.regexp eol) token 0 then
+ {list_form = list_reader.list_form; tokens = tokens}
+ else if token.[0] = ';' then
+ read_list eol { list_form = list_reader.list_form;
+ tokens = tokens }
+ else
+ let reader = read_form list_reader.tokens in
+ read_list eol {list_form = list_reader.list_form @ [reader.form];
+ tokens = reader.tokens}
+and read_quote sym tokens =
+ let reader = read_form tokens in
+ {form = Types.list [ Types.symbol sym; reader.form ];
+ tokens = reader.tokens}
+and read_form all_tokens =
+ match all_tokens with
+ | [] -> raise End_of_file;
+ | token :: tokens ->
+ match token with
+ | "'" -> read_quote "quote" tokens
+ | "`" -> read_quote "quasiquote" tokens
+ | "~" -> read_quote "unquote" tokens
+ | "~@" -> read_quote "splice-unquote" tokens
+ | "@" -> read_quote "deref" tokens
+ | "^" ->
+ let meta = read_form tokens in
+ let value = read_form meta.tokens in
+ {(*form = with_meta value.form meta.form;*)
+ form = Types.list [Types.symbol "with-meta"; value.form; meta.form];
+ tokens = value.tokens}
+ | "(" ->
+ let list_reader = read_list ")" {list_form = []; tokens = tokens} in
+ {form = Types.list list_reader.list_form;
+ tokens = list_reader.tokens}
+ | "{" ->
+ let list_reader = read_list "}" {list_form = []; tokens = tokens} in
+ {form = Types.list_into_map Types.MalMap.empty list_reader.list_form;
+ tokens = list_reader.tokens}
+ | "[" ->
+ let list_reader = read_list "]" {list_form = []; tokens = tokens} in
+ {form = Types.vector list_reader.list_form;
+ tokens = list_reader.tokens}
+ | _ -> if token.[0] = ';'
+ then read_form tokens
+ else {form = read_atom token; tokens = tokens}
+
+let read_str str = (read_form (List.filter ((<>) "") (find_re token_re str))).form
+
diff --git a/ocaml/step0_repl.ml b/ocaml/step0_repl.ml
new file mode 100644
index 0000000..e3478f7
--- /dev/null
+++ b/ocaml/step0_repl.ml
@@ -0,0 +1,23 @@
+(*
+ To try things at the ocaml repl:
+ rlwrap ocaml
+
+ To see type signatures of all functions:
+ ocamlc -i step0_repl.ml
+
+ To run the program:
+ ocaml step0_repl.ml
+*)
+
+let read str = str
+let eval ast any = ast
+let print exp = exp
+let rep str = print (eval (read str) "")
+
+let rec main =
+ try
+ while true do
+ print_string "user> ";
+ print_endline (rep (read_line ()));
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step1_read_print.ml b/ocaml/step1_read_print.ml
new file mode 100644
index 0000000..1735e11
--- /dev/null
+++ b/ocaml/step1_read_print.ml
@@ -0,0 +1,15 @@
+let read str = Reader.read_str str
+let eval ast any = ast
+let print exp = Printer.pr_str exp true
+let rep str = print (eval (read str) "")
+
+let rec main =
+ try
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line);
+ with End_of_file -> ()
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step2_eval.ml b/ocaml/step2_eval.ml
new file mode 100644
index 0000000..3778292
--- /dev/null
+++ b/ocaml/step2_eval.ml
@@ -0,0 +1,64 @@
+module T = Types.Types
+
+module Env =
+ Map.Make (
+ String
+ (*(struct
+ type t = Types.Symbol
+ let compare (Types.Symbol a) (Types.Symbol b) = compare a b
+ end)*)
+ )
+
+let num_fun f = Types.fn
+ (function
+ | [(T.Int a); (T.Int b)] -> T.Int (f a b)
+ | _ -> raise (Invalid_argument "Numeric args required for this Mal builtin"))
+
+let repl_env = ref (List.fold_left (fun a b -> b a) Env.empty
+ [ Env.add "+" (num_fun ( + ));
+ Env.add "-" (num_fun ( - ));
+ Env.add "*" (num_fun ( * ));
+ Env.add "/" (num_fun ( / )) ])
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol { T.value = s } ->
+ (try Env.find s !env
+ with Not_found -> raise (Invalid_argument ("Symbol '" ^ s ^ "' not found")))
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ let result = eval_ast ast env in
+ match result with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> (f args)
+ | _ -> result
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step3_env.ml b/ocaml/step3_env.ml
new file mode 100644
index 0000000..73d4236
--- /dev/null
+++ b/ocaml/step3_env.ml
@@ -0,0 +1,73 @@
+module T = Types.Types
+
+let num_fun f = Types.fn
+ (function
+ | [(T.Int a); (T.Int b)] -> T.Int (f a b)
+ | _ -> raise (Invalid_argument "Numeric args required for this Mal builtin"))
+
+let repl_env = Env.make None
+
+let init_repl env = begin
+ Env.set env (Types.symbol "+") (num_fun ( + ));
+ Env.set env (Types.symbol "-") (num_fun ( - ));
+ Env.set env (Types.symbol "*") (num_fun ( * ));
+ Env.set env (Types.symbol "/") (num_fun ( / ));
+end
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match ast with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List _ ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | _ -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ init_repl repl_env;
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step4_if_fn_do.ml b/ocaml/step4_if_fn_do.ml
new file mode 100644
index 0000000..f08aa55
--- /dev/null
+++ b/ocaml/step4_if_fn_do.ml
@@ -0,0 +1,83 @@
+module T = Types.Types
+
+let repl_env = Env.make (Some Core.ns)
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match ast with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List { T.value = ((T.Symbol { T.value = "do" }) :: body) } ->
+ List.fold_left (fun x expr -> eval expr env) T.Nil body
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr; else_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else (eval else_expr env)
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else T.Nil
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.Vector { T.value = arg_names }; expr] }
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.List { T.value = arg_names }; expr] } ->
+ Types.fn
+ (function args ->
+ let sub_env = Env.make (Some env) in
+ let rec bind_args a b =
+ (match a, b with
+ | [T.Symbol { T.value = "&" }; name], args -> Env.set sub_env name (Types.list args);
+ | (name :: names), (arg :: args) ->
+ Env.set sub_env name arg;
+ bind_args names args;
+ | [], [] -> ()
+ | _ -> raise (Invalid_argument "Bad param count in fn call"))
+ in bind_args arg_names args;
+ eval expr sub_env)
+ | T.List _ ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | _ -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ Core.init Core.ns;
+ ignore (rep "(def! not (fn* (a) (if a false true)))" repl_env);
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step5_tco.ml b/ocaml/step5_tco.ml
new file mode 120000
index 0000000..ab0ca0b
--- /dev/null
+++ b/ocaml/step5_tco.ml
@@ -0,0 +1 @@
+step4_if_fn_do.ml \ No newline at end of file
diff --git a/ocaml/step6_file.ml b/ocaml/step6_file.ml
new file mode 100644
index 0000000..e9d48d3
--- /dev/null
+++ b/ocaml/step6_file.ml
@@ -0,0 +1,95 @@
+module T = Types.Types
+
+let repl_env = Env.make (Some Core.ns)
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match ast with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List { T.value = ((T.Symbol { T.value = "do" }) :: body) } ->
+ List.fold_left (fun x expr -> eval expr env) T.Nil body
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr; else_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else (eval else_expr env)
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else T.Nil
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.Vector { T.value = arg_names }; expr] }
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.List { T.value = arg_names }; expr] } ->
+ Types.fn
+ (function args ->
+ let sub_env = Env.make (Some env) in
+ let rec bind_args a b =
+ (match a, b with
+ | [T.Symbol { T.value = "&" }; name], args -> Env.set sub_env name (Types.list args);
+ | (name :: names), (arg :: args) ->
+ Env.set sub_env name arg;
+ bind_args names args;
+ | [], [] -> ()
+ | _ -> raise (Invalid_argument "Bad param count in fn call"))
+ in bind_args arg_names args;
+ eval expr sub_env)
+ | T.List _ ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | _ -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ Core.init Core.ns;
+ Env.set repl_env (Types.symbol "*ARGV*")
+ (Types.list (if Array.length Sys.argv > 1
+ then (List.map (fun x -> T.String x) (List.tl (List.tl (Array.to_list Sys.argv))))
+ else []));
+ Env.set repl_env (Types.symbol "eval")
+ (Types.fn (function [ast] -> eval ast repl_env | _ -> T.Nil));
+ let code = "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+ in print_endline code; ignore (rep code repl_env);
+ ignore (rep "(def! not (fn* (a) (if a false true)))" repl_env);
+
+ if Array.length Sys.argv > 1 then
+ ignore (rep ("(load-file \"" ^ Sys.argv.(1) ^ "\")") repl_env)
+ else
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step7_quote.ml b/ocaml/step7_quote.ml
new file mode 100644
index 0000000..3291f48
--- /dev/null
+++ b/ocaml/step7_quote.ml
@@ -0,0 +1,110 @@
+module T = Types.Types
+
+let repl_env = Env.make (Some Core.ns)
+
+let rec quasiquote ast =
+ match ast with
+ | T.List { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.Vector { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.List { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail }
+ | T.Vector { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail } ->
+ Types.list [Types.symbol "concat"; head; quasiquote (Types.list tail)]
+ | T.List { T.value = head :: tail }
+ | T.Vector { T.value = head :: tail } ->
+ Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ]
+ | ast -> Types.list [Types.symbol "quote"; ast]
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match ast with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List { T.value = ((T.Symbol { T.value = "do" }) :: body) } ->
+ List.fold_left (fun x expr -> eval expr env) T.Nil body
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr; else_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else (eval else_expr env)
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else T.Nil
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.Vector { T.value = arg_names }; expr] }
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.List { T.value = arg_names }; expr] } ->
+ Types.fn
+ (function args ->
+ let sub_env = Env.make (Some env) in
+ let rec bind_args a b =
+ (match a, b with
+ | [T.Symbol { T.value = "&" }; name], args -> Env.set sub_env name (Types.list args);
+ | (name :: names), (arg :: args) ->
+ Env.set sub_env name arg;
+ bind_args names args;
+ | [], [] -> ()
+ | _ -> raise (Invalid_argument "Bad param count in fn call"))
+ in bind_args arg_names args;
+ eval expr sub_env)
+ | T.List { T.value = [T.Symbol { T.value = "quote" }; ast] } -> ast
+ | T.List { T.value = [T.Symbol { T.value = "quasiquote" }; ast] } ->
+ eval (quasiquote ast) env
+ | T.List _ ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | _ -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ Core.init Core.ns;
+ Env.set repl_env (Types.symbol "*ARGV*")
+ (Types.list (if Array.length Sys.argv > 1
+ then (List.map (fun x -> T.String x) (List.tl (List.tl (Array.to_list Sys.argv))))
+ else []));
+ Env.set repl_env (Types.symbol "eval")
+ (Types.fn (function [ast] -> eval ast repl_env | _ -> T.Nil));
+ let code = "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+ in ignore (rep code repl_env);
+ ignore (rep "(def! not (fn* (a) (if a false true)))" repl_env);
+
+ if Array.length Sys.argv > 1 then
+ ignore (rep ("(load-file \"" ^ Sys.argv.(1) ^ "\")") repl_env)
+ else
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step8_macros.ml b/ocaml/step8_macros.ml
new file mode 100644
index 0000000..7f61c59
--- /dev/null
+++ b/ocaml/step8_macros.ml
@@ -0,0 +1,144 @@
+module T = Types.Types
+
+let repl_env = Env.make (Some Core.ns)
+
+let rec quasiquote ast =
+ match ast with
+ | T.List { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.Vector { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.List { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail }
+ | T.Vector { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail } ->
+ Types.list [Types.symbol "concat"; head; quasiquote (Types.list tail)]
+ | T.List { T.value = head :: tail }
+ | T.Vector { T.value = head :: tail } ->
+ Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ]
+ | ast -> Types.list [Types.symbol "quote"; ast]
+
+let kw_macro = T.Keyword "macro"
+
+let is_macro_call ast env =
+ match ast with
+ | T.List { T.value = s :: args } ->
+ (match (try Env.get env s with _ -> T.Nil) with
+ | T.Fn { T.meta = T.Map { T.value = meta } }
+ -> Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta)
+ | _ -> false)
+ | _ -> false
+
+let rec macroexpand ast env =
+ if is_macro_call ast env
+ then match ast with
+ | T.List { T.value = s :: args } ->
+ (match (try Env.get env s with _ -> T.Nil) with
+ | T.Fn { T.value = f } -> macroexpand (f args) env
+ | _ -> ast)
+ | _ -> ast
+ else ast
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match macroexpand ast env with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "defmacro!" }); key; expr] } ->
+ (match (eval expr env) with
+ | T.Fn { T.value = f; T.meta = meta } ->
+ let fn = T.Fn { T.value = f; meta = Core.assoc [meta; kw_macro; (T.Bool true)]}
+ in Env.set env key fn; fn
+ | _ -> raise (Invalid_argument "defmacro! value must be a fn"))
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List { T.value = ((T.Symbol { T.value = "do" }) :: body) } ->
+ List.fold_left (fun x expr -> eval expr env) T.Nil body
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr; else_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else (eval else_expr env)
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else T.Nil
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.Vector { T.value = arg_names }; expr] }
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.List { T.value = arg_names }; expr] } ->
+ Types.fn
+ (function args ->
+ let sub_env = Env.make (Some env) in
+ let rec bind_args a b =
+ (match a, b with
+ | [T.Symbol { T.value = "&" }; name], args -> Env.set sub_env name (Types.list args);
+ | (name :: names), (arg :: args) ->
+ Env.set sub_env name arg;
+ bind_args names args;
+ | [], [] -> ()
+ | _ -> raise (Invalid_argument "Bad param count in fn call"))
+ in bind_args arg_names args;
+ eval expr sub_env)
+ | T.List { T.value = [T.Symbol { T.value = "quote" }; ast] } -> ast
+ | T.List { T.value = [T.Symbol { T.value = "quasiquote" }; ast] } ->
+ eval (quasiquote ast) env
+ | T.List { T.value = [T.Symbol { T.value = "macroexpand" }; ast] } ->
+ macroexpand ast env
+ | T.List _ as ast ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | ast -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ Core.init Core.ns;
+ Env.set repl_env (Types.symbol "*ARGV*")
+ (Types.list (if Array.length Sys.argv > 1
+ then (List.map (fun x -> T.String x) (List.tl (List.tl (Array.to_list Sys.argv))))
+ else []));
+ Env.set repl_env (Types.symbol "eval")
+ (Types.fn (function [ast] -> eval ast repl_env | _ -> T.Nil));
+
+ ignore (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" repl_env);
+ ignore (rep "(def! not (fn* (a) (if a false true)))" repl_env);
+ ignore (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)))))))" repl_env);
+ ignore (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))))))))" repl_env);
+
+ if Array.length Sys.argv > 1 then
+ ignore (rep ("(load-file \"" ^ Sys.argv.(1) ^ "\")") repl_env)
+ else
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ | _ ->
+ output_string stderr ("Erroringness!\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/step9_try.ml b/ocaml/step9_try.ml
new file mode 100644
index 0000000..dd220db
--- /dev/null
+++ b/ocaml/step9_try.ml
@@ -0,0 +1,156 @@
+module T = Types.Types
+
+let repl_env = Env.make (Some Core.ns)
+
+let rec quasiquote ast =
+ match ast with
+ | T.List { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.Vector { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.List { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail }
+ | T.Vector { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail } ->
+ Types.list [Types.symbol "concat"; head; quasiquote (Types.list tail)]
+ | T.List { T.value = head :: tail }
+ | T.Vector { T.value = head :: tail } ->
+ Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ]
+ | ast -> Types.list [Types.symbol "quote"; ast]
+
+let kw_macro = T.Keyword "macro"
+
+let is_macro_call ast env =
+ match ast with
+ | T.List { T.value = s :: args } ->
+ (match (try Env.get env s with _ -> T.Nil) with
+ | T.Fn { T.meta = T.Map { T.value = meta } }
+ -> Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta)
+ | _ -> false)
+ | _ -> false
+
+let rec macroexpand ast env =
+ if is_macro_call ast env
+ then match ast with
+ | T.List { T.value = s :: args } ->
+ (match (try Env.get env s with _ -> T.Nil) with
+ | T.Fn { T.value = f } -> macroexpand (f args) env
+ | _ -> ast)
+ | _ -> ast
+ else ast
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match macroexpand ast env with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "defmacro!" }); key; expr] } ->
+ (match (eval expr env) with
+ | T.Fn { T.value = f; T.meta = meta } ->
+ let fn = T.Fn { T.value = f; meta = Core.assoc [meta; kw_macro; (T.Bool true)]}
+ in Env.set env key fn; fn
+ | _ -> raise (Invalid_argument "devmacro! value must be a fn"))
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List { T.value = ((T.Symbol { T.value = "do" }) :: body) } ->
+ List.fold_left (fun x expr -> eval expr env) T.Nil body
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr; else_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else (eval else_expr env)
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else T.Nil
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.Vector { T.value = arg_names }; expr] }
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.List { T.value = arg_names }; expr] } ->
+ Types.fn
+ (function args ->
+ let sub_env = Env.make (Some env) in
+ let rec bind_args a b =
+ (match a, b with
+ | [T.Symbol { T.value = "&" }; name], args -> Env.set sub_env name (Types.list args);
+ | (name :: names), (arg :: args) ->
+ Env.set sub_env name arg;
+ bind_args names args;
+ | [], [] -> ()
+ | _ -> raise (Invalid_argument "Bad param count in fn call"))
+ in bind_args arg_names args;
+ eval expr sub_env)
+ | T.List { T.value = [T.Symbol { T.value = "quote" }; ast] } -> ast
+ | T.List { T.value = [T.Symbol { T.value = "quasiquote" }; ast] } ->
+ eval (quasiquote ast) env
+ | T.List { T.value = [T.Symbol { T.value = "macroexpand" }; ast] } ->
+ macroexpand ast env
+ | T.List { T.value = [T.Symbol { T.value = "try*" }; scary ;
+ T.List { T.value = [T.Symbol { T.value = "catch*" };
+ local ; handler]}]} ->
+ (try (eval scary env)
+ with exn ->
+ let value = match exn with
+ | Types.MalExn value -> value
+ | Invalid_argument msg -> T.String msg
+ | _ -> (T.String "OCaml exception") in
+ let sub_env = Env.make (Some env) in
+ Env.set sub_env local value;
+ eval handler sub_env)
+ | T.List _ as ast ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | ast -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ Core.init Core.ns;
+ Env.set repl_env (Types.symbol "*ARGV*")
+ (Types.list (if Array.length Sys.argv > 1
+ then (List.map (fun x -> T.String x) (List.tl (List.tl (Array.to_list Sys.argv))))
+ else []));
+ Env.set repl_env (Types.symbol "eval")
+ (Types.fn (function [ast] -> eval ast repl_env | _ -> T.Nil));
+
+ ignore (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" repl_env);
+ ignore (rep "(def! not (fn* (a) (if a false true)))" repl_env);
+ ignore (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)))))))" repl_env);
+ ignore (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))))))))" repl_env);
+
+ if Array.length Sys.argv > 1 then
+ ignore (rep ("(load-file \"" ^ Sys.argv.(1) ^ "\")") repl_env)
+ else
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ | _ ->
+ output_string stderr ("Erroringness!\n");
+ flush stderr
+ done
+ with End_of_file -> ()
diff --git a/ocaml/stepA_mal.ml b/ocaml/stepA_mal.ml
new file mode 100644
index 0000000..1aab28a
--- /dev/null
+++ b/ocaml/stepA_mal.ml
@@ -0,0 +1,159 @@
+module T = Types.Types
+
+let repl_env = Env.make (Some Core.ns)
+
+let rec quasiquote ast =
+ match ast with
+ | T.List { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.Vector { T.value = [T.Symbol {T.value = "unquote"}; ast] } -> ast
+ | T.List { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail }
+ | T.Vector { T.value = T.List { T.value = [T.Symbol {T.value = "splice-unquote"}; head]} :: tail } ->
+ Types.list [Types.symbol "concat"; head; quasiquote (Types.list tail)]
+ | T.List { T.value = head :: tail }
+ | T.Vector { T.value = head :: tail } ->
+ Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ]
+ | ast -> Types.list [Types.symbol "quote"; ast]
+
+let kw_macro = T.Keyword "macro"
+
+let is_macro_call ast env =
+ match ast with
+ | T.List { T.value = s :: args } ->
+ (match (try Env.get env s with _ -> T.Nil) with
+ | T.Fn { T.meta = T.Map { T.value = meta } }
+ -> Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta)
+ | _ -> false)
+ | _ -> false
+
+let rec macroexpand ast env =
+ if is_macro_call ast env
+ then match ast with
+ | T.List { T.value = s :: args } ->
+ (match (try Env.get env s with _ -> T.Nil) with
+ | T.Fn { T.value = f } -> macroexpand (f args) env
+ | _ -> ast)
+ | _ -> ast
+ else ast
+
+let rec eval_ast ast env =
+ match ast with
+ | T.Symbol s -> Env.get env ast
+ | T.List { T.value = xs; T.meta = meta }
+ -> T.List { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Vector { T.value = xs; T.meta = meta }
+ -> T.Vector { T.value = (List.map (fun x -> eval x env) xs);
+ T.meta = meta }
+ | T.Map { T.value = xs; T.meta = meta }
+ -> T.Map {T.meta = meta;
+ T.value = (Types.MalMap.fold
+ (fun k v m
+ -> Types.MalMap.add (eval k env) (eval v env) m)
+ xs
+ Types.MalMap.empty)}
+ | _ -> ast
+and eval ast env =
+ match macroexpand ast env with
+ | T.List { T.value = [(T.Symbol { T.value = "def!" }); key; expr] } ->
+ let value = (eval expr env) in
+ Env.set env key value; value
+ | T.List { T.value = [(T.Symbol { T.value = "defmacro!" }); key; expr] } ->
+ (match (eval expr env) with
+ | T.Fn { T.value = f; T.meta = meta } ->
+ let fn = T.Fn { T.value = f; meta = Core.assoc [meta; kw_macro; (T.Bool true)]}
+ in Env.set env key fn; fn
+ | _ -> raise (Invalid_argument "devmacro! value must be a fn"))
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] }
+ | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.List { T.value = bindings }); body] } ->
+ (let sub_env = Env.make (Some env) in
+ let rec bind_pairs = (function
+ | sym :: expr :: more ->
+ Env.set sub_env sym (eval expr sub_env);
+ bind_pairs more
+ | _::[] -> raise (Invalid_argument "let* bindings must be an even number of forms")
+ | [] -> ())
+ in bind_pairs bindings;
+ eval body sub_env)
+ | T.List { T.value = ((T.Symbol { T.value = "do" }) :: body) } ->
+ List.fold_left (fun x expr -> eval expr env) T.Nil body
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr; else_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else (eval else_expr env)
+ | T.List { T.value = [T.Symbol { T.value = "if" }; test; then_expr] } ->
+ if Types.to_bool (eval test env) then (eval then_expr env) else T.Nil
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.Vector { T.value = arg_names }; expr] }
+ | T.List { T.value = [T.Symbol { T.value = "fn*" }; T.List { T.value = arg_names }; expr] } ->
+ Types.fn
+ (function args ->
+ let sub_env = Env.make (Some env) in
+ let rec bind_args a b =
+ (match a, b with
+ | [T.Symbol { T.value = "&" }; name], args -> Env.set sub_env name (Types.list args);
+ | (name :: names), (arg :: args) ->
+ Env.set sub_env name arg;
+ bind_args names args;
+ | [], [] -> ()
+ | _ -> raise (Invalid_argument "Bad param count in fn call"))
+ in bind_args arg_names args;
+ eval expr sub_env)
+ | T.List { T.value = [T.Symbol { T.value = "quote" }; ast] } -> ast
+ | T.List { T.value = [T.Symbol { T.value = "quasiquote" }; ast] } ->
+ eval (quasiquote ast) env
+ | T.List { T.value = [T.Symbol { T.value = "macroexpand" }; ast] } ->
+ macroexpand ast env
+ | T.List { T.value = [T.Symbol { T.value = "try*" }; scary ;
+ T.List { T.value = [T.Symbol { T.value = "catch*" };
+ local ; handler]}]} ->
+ (try (eval scary env)
+ with exn ->
+ let value = match exn with
+ | Types.MalExn value -> value
+ | Invalid_argument msg -> T.String msg
+ | _ -> (T.String "OCaml exception") in
+ let sub_env = Env.make (Some env) in
+ Env.set sub_env local value;
+ eval handler sub_env)
+ | T.List _ as ast ->
+ (match eval_ast ast env with
+ | T.List { T.value = ((T.Fn { T.value = f }) :: args) } -> f args
+ | _ -> raise (Invalid_argument "Cannot invoke non-function"))
+ | ast -> eval_ast ast env
+
+let read str = Reader.read_str str
+let print exp = Printer.pr_str exp true
+let rep str env = print (eval (read str) env)
+
+let rec main =
+ try
+ Core.init Core.ns;
+ Env.set repl_env (Types.symbol "*ARGV*")
+ (Types.list (if Array.length Sys.argv > 1
+ then (List.map (fun x -> T.String x) (List.tl (List.tl (Array.to_list Sys.argv))))
+ else []));
+ Env.set repl_env (Types.symbol "eval")
+ (Types.fn (function [ast] -> eval ast repl_env | _ -> T.Nil));
+
+ ignore (rep "(def! *host-language* \"ocaml\")" repl_env);
+ ignore (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" repl_env);
+ ignore (rep "(def! not (fn* (a) (if a false true)))" repl_env);
+ ignore (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)))))))" repl_env);
+ ignore (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))))))))" repl_env);
+
+ if Array.length Sys.argv > 1 then
+ ignore (rep ("(load-file \"" ^ Sys.argv.(1) ^ "\")") repl_env)
+ else begin
+ ignore (rep "(println (str \"Mal [\" *host-language* \"]\"))" repl_env);
+ while true do
+ print_string "user> ";
+ let line = read_line () in
+ try
+ print_endline (rep line repl_env);
+ with End_of_file -> ()
+ | Invalid_argument x ->
+ output_string stderr ("Invalid_argument exception: " ^ x ^ "\n");
+ flush stderr
+ | _ ->
+ output_string stderr ("Erroringness!\n");
+ flush stderr
+ done
+ end
+ with End_of_file -> ()
diff --git a/ocaml/types.ml b/ocaml/types.ml
new file mode 100644
index 0000000..9df9761
--- /dev/null
+++ b/ocaml/types.ml
@@ -0,0 +1,50 @@
+module rec Types
+ : sig
+ type 'a with_meta = { value : 'a; meta : t }
+ and t =
+ | List of t list with_meta
+ | Vector of t list with_meta
+ | Map of t MalMap.t with_meta
+ | Int of int
+ | Symbol of string with_meta
+ | Keyword of string
+ | Nil
+ | Bool of bool
+ | String of string
+ | Fn of (t list -> t) with_meta
+ | Atom of t ref
+ end = Types
+
+and MalValue
+ : sig
+ type t = Types.t
+ val compare : t -> t -> int
+ end
+ = struct
+ type t = Types.t
+ let compare = Pervasives.compare
+ end
+
+and MalMap
+ : Map.S with type key = MalValue.t
+ = Map.Make(MalValue)
+
+exception MalExn of Types.t
+
+let to_bool x = match x with
+ | Types.Nil | Types.Bool false -> false
+ | _ -> true
+
+type mal_type = MalValue.t
+
+let list x = Types.List { Types.value = x; meta = Types.Nil }
+let map x = Types.Map { Types.value = x; meta = Types.Nil }
+let vector x = Types.Vector { Types.value = x; meta = Types.Nil }
+let symbol x = Types.Symbol { Types.value = x; meta = Types.Nil }
+let fn f = Types.Fn { Types.value = f; meta = Types.Nil }
+
+let rec list_into_map target source =
+ match source with
+ | k :: v :: more -> list_into_map (MalMap.add k v target) more
+ | [] -> map target
+ | _ :: [] -> raise (Invalid_argument "Literal maps must contain an even number of forms")
diff --git a/perf.mal b/perf.mal
new file mode 100644
index 0000000..83bbc0d
--- /dev/null
+++ b/perf.mal
@@ -0,0 +1,27 @@
+(defmacro! time
+ (fn* (exp)
+ `(let* (start_FIXME (time-ms)
+ ret_FIXME ~exp)
+ (do
+ (prn (str "Elapsed time: " (- (time-ms) start_FIXME) " msecs"))
+ ret_FIXME))))
+
+(def! run-fn-for*
+ (fn* [fn max-ms acc-ms iters]
+ (let* [start (time-ms)
+ _ (fn)
+ elapsed (- (time-ms) start)
+ new-iters (+ 1 iters)
+ new-acc-ms (+ acc-ms elapsed)]
+ ;(do (prn "here:" new-acc-ms "/" max-ms "iters:" new-iters) )
+ (if (>= new-acc-ms max-ms)
+ (/ (* max-ms iters) new-acc-ms)
+ (run-fn-for* fn max-ms new-acc-ms new-iters)))))
+
+(def! run-fn-for
+ (fn* [fn max-secs]
+ (do
+ ;; Warm it up first
+ (run-fn-for* fn 1000 0 0)
+ ;; Now do the test
+ (/ (run-fn-for* fn (* 1000 max-secs) 0 0) 3))))
diff --git a/perl/Makefile b/perl/Makefile
index 772bba7..0aed9c6 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -2,7 +2,7 @@ TESTS =
SOURCES_BASE = readline.pm types.pm reader.pm printer.pm \
interop.pm
-SOURCES_LISP = env.pm core.pm stepA_more.pl
+SOURCES_LISP = env.pm core.pm stepA_mal.pl
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
#all: mal.pl
diff --git a/perl/core.pm b/perl/core.pm
index eeee77e..7d70278 100644
--- a/perl/core.pm
+++ b/perl/core.pm
@@ -7,7 +7,8 @@ use Time::HiRes qw(time);
use readline;
use types qw(_sequential_Q _equal_Q _clone $nil $true $false
- _symbol_Q _nil_Q _true_Q _false_Q _list_Q _vector_Q
+ _nil_Q _true_Q _false_Q
+ _symbol _symbol_Q _keyword _keyword_Q _list_Q _vector_Q
_hash_map _hash_map_Q _assoc_BANG _dissoc_BANG _atom_Q);
use reader qw(read_str);
use printer qw(_pr_str);
@@ -101,12 +102,27 @@ sub concat {
List->new(\@new_arr);
}
-sub nth { my ($seq,$i) = @_; return scalar(@{$seq->{val}}) > $i ? $seq->nth($i) : $nil; }
+sub nth {
+ my ($seq,$i) = @_;
+ if (@{$seq->{val}} > $i) {
+ return scalar($seq->nth($i));
+ } else {
+ die "nth: index out of bounds";
+ }
+}
sub first { my ($seq) = @_; return scalar(@{$seq->{val}}) > 0 ? $seq->nth(0) : $nil; }
sub rest { return $_[0]->rest(); }
+sub count {
+ if (_nil_Q($_[0])) {
+ return Integer->new(0);
+ } else {
+ return Integer->new(scalar(@{$_[0]->{val}}))
+ }
+}
+
sub apply {
my @all_args = @{$_[0]->{val}};
my $f = $all_args[0];
@@ -167,7 +183,10 @@ our $core_ns = {
'nil?' => sub { _nil_Q($_[0]->nth(0)) ? $true : $false },
'true?' => sub { _true_Q($_[0]->nth(0)) ? $true : $false },
'false?' => sub { _false_Q($_[0]->nth(0)) ? $true : $false },
+ 'symbol' => sub { Symbol->new(${$_[0]->nth(0)}) },
'symbol?' => sub { _symbol_Q($_[0]->nth(0)) ? $true : $false },
+ 'keyword' => sub { _keyword(${$_[0]->nth(0)}) },
+ 'keyword?' => sub { _keyword_Q($_[0]->nth(0)) ? $true : $false },
'pr-str' => sub { pr_str($_[0]) },
'str' => sub { str($_[0]) },
@@ -206,7 +225,7 @@ our $core_ns = {
'cons' => sub { cons($_[0]->nth(0), $_[0]->nth(1)) },
'concat' => sub { concat(@{$_[0]->{val}}) },
'empty?' => sub { scalar(@{$_[0]->nth(0)->{val}}) == 0 ? $true : $false },
- 'count' => sub { Integer->new(scalar(@{$_[0]->nth(0)->{val}})) },
+ 'count' => sub { count($_[0]->nth(0)) },
'apply' => sub { apply($_[0]) },
'map' => sub { mal_map($_[0]->nth(0), $_[0]->nth(1)) },
'conj' => sub { die "not implemented\n"; },
diff --git a/perl/env.pm b/perl/env.pm
index 372eecd..8012565 100644
--- a/perl/env.pm
+++ b/perl/env.pm
@@ -28,20 +28,20 @@ use Exporter 'import';
}
sub find {
my ($self, $key) = @_;
- if (exists $self->{$key}) { return $self; }
+ if (exists $self->{$$key}) { return $self; }
elsif ($self->{__outer__}) { return $self->{__outer__}->find($key); }
else { return undef; }
}
sub set {
my ($self, $key, $value) = @_;
- $self->{$key} = $value;
+ $self->{$$key} = $value;
return $value
}
sub get {
my ($self, $key) = @_;
my $env = $self->find($key);
- die "'" . $key . "' not found\n" unless $env;
- return $env->{$key};
+ die "'" . $$key . "' not found\n" unless $env;
+ return $env->{$$key};
}
}
diff --git a/perl/interop.pm b/perl/interop.pm
index 44657ec..ffa379f 100644
--- a/perl/interop.pm
+++ b/perl/interop.pm
@@ -1,6 +1,7 @@
package interop;
use strict;
use warnings FATAL => qw(all);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
use feature qw(switch);
use Exporter 'import';
our @EXPORT_OK = qw( pl_to_mal );
diff --git a/perl/printer.pm b/perl/printer.pm
index e31bed5..7b00b1e 100644
--- a/perl/printer.pm
+++ b/perl/printer.pm
@@ -1,6 +1,7 @@
package printer;
use strict;
use warnings FATAL => qw(all);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
use feature qw(switch);
use Exporter 'import';
our @EXPORT_OK = qw( _pr_str );
@@ -29,7 +30,9 @@ sub _pr_str {
return '{' . join(' ', @elems) . '}';
}
when(/^String/) {
- if ($_r) {
+ if ($$obj =~ /^\x{029e}/) {
+ return ':' . substr($$obj,1);
+ } elsif ($_r) {
my $str = $$obj;
$str =~ s/\\/\\\\/g;
$str =~ s/"/\\"/g;
diff --git a/perl/reader.pm b/perl/reader.pm
index e49d5a1..501f992 100644
--- a/perl/reader.pm
+++ b/perl/reader.pm
@@ -2,10 +2,11 @@ package reader;
use feature qw(switch);
use strict;
use warnings FATAL => qw(all);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
use Exporter 'import';
our @EXPORT_OK = qw( read_str );
-use types qw($nil $true $false _hash_map);
+use types qw($nil $true $false _keyword _hash_map);
use Data::Dumper;
@@ -36,6 +37,7 @@ sub read_atom {
$str =~ s/\\n/\n/g;
return String->new($str)
}
+ when(/^:/) { return _keyword(substr($token,1)) }
when(/^nil$/) { return $nil }
when(/^true$/) { return $true }
when(/^false$/) { return $false }
diff --git a/perl/readline.pm b/perl/readline.pm
index f0710b1..0629f39 100644
--- a/perl/readline.pm
+++ b/perl/readline.pm
@@ -1,12 +1,12 @@
# To get readline line editing functionality, please install
-# Term::ReadLine::Gnu (GPL) or Term::ReadLine::Perl (GPL, Artistic)
-# from CPAN.
+# Term::ReadKey and either Term::ReadLine::Gnu (GPL) or
+# Term::ReadLine::Perl (GPL, Artistic) from CPAN.
package readline;
use strict;
use warnings;
use Exporter 'import';
-our @EXPORT_OK = qw( mal_readline );
+our @EXPORT_OK = qw( mal_readline set_rl_mode );
use Term::ReadLine;
@@ -36,6 +36,13 @@ sub load_history {
close $fh;
}
+my $rl_mode = "terminal";
+
+sub set_rl_mode {
+ my($mode) = @_;
+ $rl_mode = $mode;
+}
+
sub mal_readline {
my($prompt) = @_;
my $line = undef;
@@ -44,11 +51,21 @@ sub mal_readline {
load_history();
}
- if (defined ($line = $_rl->readline($prompt))) {
- save_line($line);
- return $line;
+ if ($rl_mode eq "terminal") {
+ if (defined ($line = $_rl->readline($prompt))) {
+ save_line($line);
+ return $line;
+ } else {
+ return undef;
+ }
} else {
- return undef;
+ print "$prompt";
+ if (defined ($line = readline(*STDIN))) {
+ save_line($line);
+ return $line;
+ } else {
+ return undef;
+ }
}
}
1;
diff --git a/perl/step0.5_repl.pl b/perl/step0.5_repl.pl
new file mode 100644
index 0000000..d8a9d9f
--- /dev/null
+++ b/perl/step0.5_repl.pl
@@ -0,0 +1,33 @@
+use strict;
+use warnings FATAL => qw(all);
+use readline qw(readline);
+
+# read
+sub READ {
+ my $str = shift;
+ return $str;
+}
+
+# eval
+sub EVAL {
+ my($ast, $env) = @_;
+ return eval($ast);
+}
+
+# print
+sub PRINT {
+ my $exp = shift;
+ return $exp;
+}
+
+# repl
+sub REP {
+ my $str = shift;
+ return PRINT(EVAL(READ($str), {}));
+}
+
+while (1) {
+ my $line = readline("user> ");
+ if (! defined $line) { last; }
+ print(REP($line), "\n");
+}
diff --git a/perl/step0_repl.pl b/perl/step0_repl.pl
index d20b167..36d6375 100644
--- a/perl/step0_repl.pl
+++ b/perl/step0_repl.pl
@@ -1,6 +1,8 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(readline);
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline);
# read
sub READ {
@@ -27,7 +29,7 @@ sub REP {
}
while (1) {
- my $line = readline("user> ");
+ my $line = mal_readline("user> ");
if (! defined $line) { last; }
print(REP($line), "\n");
}
diff --git a/perl/step1_read_print.pl b/perl/step1_read_print.pl
index 988c307..26c7bbf 100644
--- a/perl/step1_read_print.pl
+++ b/perl/step1_read_print.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use reader;
@@ -30,6 +33,9 @@ sub REP {
return PRINT(EVAL(READ($str), {}));
}
+if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+}
while (1) {
my $line = mal_readline("user> ");
if (! defined $line) { last; }
diff --git a/perl/step2_eval.pl b/perl/step2_eval.pl
index 1655a29..858a385 100644
--- a/perl/step2_eval.pl
+++ b/perl/step2_eval.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -77,6 +80,9 @@ $repl_env->{'-'} = sub { Integer->new(${$_[0]->nth(0)} - ${$_[0]->nth(1)}) };
$repl_env->{'*'} = sub { Integer->new(${$_[0]->nth(0)} * ${$_[0]->nth(1)}) };
$repl_env->{'/'} = sub { Integer->new(${$_[0]->nth(0)} / ${$_[0]->nth(1)}) };
+if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+}
while (1) {
my $line = mal_readline("user> ");
if (! defined $line) { last; }
diff --git a/perl/step3_env.pl b/perl/step3_env.pl
index c0e722b..1c34ab6 100644
--- a/perl/step3_env.pl
+++ b/perl/step3_env.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -20,7 +23,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -55,12 +58,12 @@ sub EVAL {
given ($$a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
return EVAL($a2, $let_env);
}
@@ -85,11 +88,14 @@ sub REP {
return PRINT(EVAL(READ($str), $repl_env));
}
-$repl_env->set('+', sub { Integer->new(${$_[0]->nth(0)} + ${$_[0]->nth(1)}) } );
-$repl_env->set('-', sub { Integer->new(${$_[0]->nth(0)} - ${$_[0]->nth(1)}) } );
-$repl_env->set('*', sub { Integer->new(${$_[0]->nth(0)} * ${$_[0]->nth(1)}) } );
-$repl_env->set('/', sub { Integer->new(${$_[0]->nth(0)} / ${$_[0]->nth(1)}) } );
+$repl_env->set(Symbol->new('+'), sub { Integer->new(${$_[0]->nth(0)} + ${$_[0]->nth(1)}) } );
+$repl_env->set(Symbol->new('-'), sub { Integer->new(${$_[0]->nth(0)} - ${$_[0]->nth(1)}) } );
+$repl_env->set(Symbol->new('*'), sub { Integer->new(${$_[0]->nth(0)} * ${$_[0]->nth(1)}) } );
+$repl_env->set(Symbol->new('/'), sub { Integer->new(${$_[0]->nth(0)} / ${$_[0]->nth(1)}) } );
+if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+}
while (1) {
my $line = mal_readline("user> ");
if (! defined $line) { last; }
diff --git a/perl/step4_if_fn_do.pl b/perl/step4_if_fn_do.pl
index 8771155..64ad314 100644
--- a/perl/step4_if_fn_do.pl
+++ b/perl/step4_if_fn_do.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -21,7 +24,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -56,12 +59,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
return EVAL($a2, $let_env);
}
@@ -106,11 +109,16 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))");
+if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+}
while (1) {
my $line = mal_readline("user> ");
if (! defined $line) { last; }
diff --git a/perl/step5_tco.pl b/perl/step5_tco.pl
index 44de718..53255c2 100644
--- a/perl/step5_tco.pl
+++ b/perl/step5_tco.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -21,7 +24,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -59,12 +62,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
$ast = $a2;
$env = $let_env;
@@ -117,11 +120,16 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))");
+if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+}
while (1) {
my $line = mal_readline("user> ");
if (! defined $line) { last; }
diff --git a/perl/step6_file.pl b/perl/step6_file.pl
index 9fcac1d..48b835f 100644
--- a/perl/step6_file.pl
+++ b/perl/step6_file.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -21,7 +24,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -59,12 +62,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
$ast = $a2;
$env = $let_env;
@@ -117,15 +120,21 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
-$repl_env->set('eval', sub { EVAL($_[0]->nth(0), $repl_env); });
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
+$repl_env->set(Symbol->new('eval'), sub { EVAL($_[0]->nth(0), $repl_env); });
my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
-$repl_env->set('*ARGV*', List->new(\@_argv));
+$repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv));
# 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 (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+ shift @ARGV;
+}
if (scalar(@ARGV) > 0) {
REP("(load-file \"" . $ARGV[0] . "\")");
exit 0;
diff --git a/perl/step7_quote.pl b/perl/step7_quote.pl
index 19c0599..89133a2 100644
--- a/perl/step7_quote.pl
+++ b/perl/step7_quote.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -44,7 +47,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -82,12 +85,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
$ast = $a2;
$env = $let_env;
@@ -147,15 +150,21 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
-$repl_env->set('eval', sub { EVAL($_[0]->nth(0), $repl_env); });
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
+$repl_env->set(Symbol->new('eval'), sub { EVAL($_[0]->nth(0), $repl_env); });
my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
-$repl_env->set('*ARGV*', List->new(\@_argv));
+$repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv));
# 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 (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+ shift @ARGV;
+}
if (scalar(@ARGV) > 0) {
REP("(load-file \"" . $ARGV[0] . "\")");
exit 0;
diff --git a/perl/step8_macros.pl b/perl/step8_macros.pl
index 47004a2..4e4a48d 100644
--- a/perl/step8_macros.pl
+++ b/perl/step8_macros.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -44,8 +47,8 @@ sub is_macro_call {
my ($ast, $env) = @_;
if (_list_Q($ast) &&
_symbol_Q($ast->nth(0)) &&
- $env->find(${$ast->nth(0)})) {
- my ($f) = $env->get(${$ast->nth(0)});
+ $env->find($ast->nth(0))) {
+ my ($f) = $env->get($ast->nth(0));
if ((ref $f) =~ /^Function/) {
return $f->{ismacro};
}
@@ -56,7 +59,7 @@ sub is_macro_call {
sub macroexpand {
my ($ast, $env) = @_;
while (is_macro_call($ast, $env)) {
- my $mac = $env->get(${$ast->nth(0)});
+ my $mac = $env->get($ast->nth(0));
$ast = $mac->apply($ast->rest());
}
return $ast;
@@ -67,7 +70,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -108,12 +111,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
$ast = $a2;
$env = $let_env;
@@ -129,7 +132,7 @@ sub EVAL {
when (/^defmacro!$/) {
my $func = EVAL($a2, $env);
$func->{ismacro} = 1;
- return $env->set($$a1, $func);
+ return $env->set($a1, $func);
}
when (/^macroexpand$/) {
return macroexpand($a1, $env);
@@ -181,15 +184,24 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
-$repl_env->set('eval', sub { EVAL($_[0]->nth(0), $repl_env); });
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
+$repl_env->set(Symbol->new('eval'), sub { EVAL($_[0]->nth(0), $repl_env); });
my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
-$repl_env->set('*ARGV*', List->new(\@_argv));
+$repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv));
# 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 (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+ shift @ARGV;
+}
if (scalar(@ARGV) > 0) {
REP("(load-file \"" . $ARGV[0] . "\")");
exit 0;
diff --git a/perl/step9_interop.pl b/perl/step9_try.pl
index 45dd4af..ec823bc 100644
--- a/perl/step9_interop.pl
+++ b/perl/step9_try.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -45,8 +48,8 @@ sub is_macro_call {
my ($ast, $env) = @_;
if (_list_Q($ast) &&
_symbol_Q($ast->nth(0)) &&
- $env->find(${$ast->nth(0)})) {
- my ($f) = $env->get(${$ast->nth(0)});
+ $env->find($ast->nth(0))) {
+ my ($f) = $env->get($ast->nth(0));
if ((ref $f) =~ /^Function/) {
return $f->{ismacro};
}
@@ -57,7 +60,7 @@ sub is_macro_call {
sub macroexpand {
my ($ast, $env) = @_;
while (is_macro_call($ast, $env)) {
- my $mac = $env->get(${$ast->nth(0)});
+ my $mac = $env->get($ast->nth(0));
$ast = $mac->apply($ast->rest());
}
return $ast;
@@ -68,7 +71,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -109,12 +112,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
$ast = $a2;
$env = $let_env;
@@ -130,13 +133,37 @@ sub EVAL {
when (/^defmacro!$/) {
my $func = EVAL($a2, $env);
$func->{ismacro} = 1;
- return $env->set($$a1, $func);
+ return $env->set($a1, $func);
}
when (/^macroexpand$/) {
return macroexpand($a1, $env);
}
- when (/^pl\*$/) {
- return pl_to_mal(eval(${$a1}));
+ when (/^try\*$/) {
+ do {
+ local $@;
+ my $ret;
+ eval {
+ use autodie; # always "throw" errors
+ $ret = EVAL($a1, $env);
+ 1;
+ } or do {
+ my $err = $@;
+ if ($a2 && ${$a2->nth(0)} eq "catch\*") {
+ my $exc;
+ if (ref $err) {
+ $exc = $err;
+ } else {
+ $exc = String->new(substr $err, 0, -1);
+ }
+ return EVAL($a2->nth(2), Env->new($env,
+ List->new([$a2->nth(1)]),
+ List->new([$exc])));
+ } else {
+ die $err;
+ }
+ };
+ return $ret;
+ };
}
when (/^do$/) {
eval_ast($ast->slice(1, $#{$ast->{val}}-1), $env);
@@ -185,15 +212,24 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
-$repl_env->set('eval', sub { EVAL($_[0]->nth(0), $repl_env); });
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
+$repl_env->set(Symbol->new('eval'), sub { EVAL($_[0]->nth(0), $repl_env); });
my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
-$repl_env->set('*ARGV*', List->new(\@_argv));
+$repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv));
# 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 (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+ shift @ARGV;
+}
if (scalar(@ARGV) > 0) {
REP("(load-file \"" . $ARGV[0] . "\")");
exit 0;
diff --git a/perl/stepA_more.pl b/perl/stepA_mal.pl
index eca2b6e..7993635 100644
--- a/perl/stepA_more.pl
+++ b/perl/stepA_mal.pl
@@ -1,6 +1,9 @@
use strict;
use warnings FATAL => qw(all);
-use readline qw(mal_readline);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
+use File::Basename;
+use lib dirname (__FILE__);
+use readline qw(mal_readline set_rl_mode);
use feature qw(switch);
use Data::Dumper;
@@ -45,8 +48,8 @@ sub is_macro_call {
my ($ast, $env) = @_;
if (_list_Q($ast) &&
_symbol_Q($ast->nth(0)) &&
- $env->find(${$ast->nth(0)})) {
- my ($f) = $env->get(${$ast->nth(0)});
+ $env->find($ast->nth(0))) {
+ my ($f) = $env->get($ast->nth(0));
if ((ref $f) =~ /^Function/) {
return $f->{ismacro};
}
@@ -57,7 +60,7 @@ sub is_macro_call {
sub macroexpand {
my ($ast, $env) = @_;
while (is_macro_call($ast, $env)) {
- my $mac = $env->get(${$ast->nth(0)});
+ my $mac = $env->get($ast->nth(0));
$ast = $mac->apply($ast->rest());
}
return $ast;
@@ -68,7 +71,7 @@ sub eval_ast {
my($ast, $env) = @_;
given (ref $ast) {
when (/^Symbol/) {
- $env->get($$ast);
+ $env->get($ast);
}
when (/^List/) {
my @lst = map {EVAL($_, $env)} @{$ast->{val}};
@@ -109,12 +112,12 @@ sub EVAL {
given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
when (/^def!$/) {
my $res = EVAL($a2, $env);
- return $env->set($$a1, $res);
+ return $env->set($a1, $res);
}
when (/^let\*$/) {
my $let_env = Env->new($env);
for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
- $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
+ $let_env->set($a1->nth($i), EVAL($a1->nth($i+1), $let_env));
}
$ast = $a2;
$env = $let_env;
@@ -130,7 +133,7 @@ sub EVAL {
when (/^defmacro!$/) {
my $func = EVAL($a2, $env);
$func->{ismacro} = 1;
- return $env->set($$a1, $func);
+ return $env->set($a1, $func);
}
when (/^macroexpand$/) {
return macroexpand($a1, $env);
@@ -212,10 +215,12 @@ sub REP {
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
-$repl_env->set('eval', sub { EVAL($_[0]->nth(0), $repl_env); });
+foreach my $n (%$core_ns) {
+ $repl_env->set(Symbol->new($n), $core_ns->{$n});
+}
+$repl_env->set(Symbol->new('eval'), sub { EVAL($_[0]->nth(0), $repl_env); });
my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
-$repl_env->set('*ARGV*', List->new(\@_argv));
+$repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv));
# core.mal: defined using the language itself
REP("(def! *host-language* \"javascript\")");
@@ -225,6 +230,10 @@ REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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 (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+ set_rl_mode("raw");
+ shift @ARGV;
+}
if (scalar(@ARGV) > 0) {
REP("(load-file \"" . $ARGV[0] . "\")");
exit 0;
diff --git a/perl/tests/step9_interop.mal b/perl/tests/stepA_mal.mal
index 1335be4..1335be4 100644
--- a/perl/tests/step9_interop.mal
+++ b/perl/tests/stepA_mal.mal
diff --git a/perl/types.pm b/perl/types.pm
index e551e11..e2d919c 100644
--- a/perl/types.pm
+++ b/perl/types.pm
@@ -1,13 +1,13 @@
package types;
use strict;
use warnings FATAL => qw(all);
+no if $] >= 5.018, warnings => "experimental::smartmatch";
use feature qw(switch);
use Exporter 'import';
our @EXPORT_OK = qw(_sequential_Q _equal_Q _clone
- $nil $true $false
- _symbol_Q _nil_Q _true_Q _false_Q _list_Q _vector_Q
- _hash_map _hash_map_Q _assoc_BANG _dissoc_BANG
- _atom_Q);
+ $nil $true $false _nil_Q _true_Q _false_Q
+ _symbol _symbol_Q _keyword _keyword_Q _list_Q _vector_Q
+ _hash_map _hash_map_Q _assoc_BANG _dissoc_BANG _atom_Q);
use Data::Dumper;
@@ -110,10 +110,13 @@ sub _false_Q { return $_[0] eq $false }
package Symbol;
sub new { my $class = shift; bless \$_[0] => $class }
}
-
sub _symbol_Q { (ref $_[0]) =~ /^Symbol/ }
+sub _keyword { return String->new(("\x{029e}".$_[0])); }
+sub _keyword_Q { ((ref $_[0]) =~ /^String/) && ${$_[0]} =~ /^\x{029e}/; }
+
+
{
package String;
sub new { my $class = shift; bless \$_[0] => $class }
diff --git a/php/Makefile b/php/Makefile
index 9b91421..659e89c 100644
--- a/php/Makefile
+++ b/php/Makefile
@@ -2,7 +2,7 @@
TESTS =
SOURCES_BASE = readline.php types.php reader.php printer.php
-SOURCES_LISP = env.php core.php stepA_more.php
+SOURCES_LISP = env.php core.php stepA_mal.php
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
.PHONY: stats tests $(TESTS)
diff --git a/php/core.php b/php/core.php
index 4cb4667..96129b1 100644
--- a/php/core.php
+++ b/php/core.php
@@ -97,7 +97,11 @@ function concat() {
}
function nth($seq, $idx) {
- return $seq[$idx];
+ if ($idx < $seq->count()) {
+ return $seq[$idx];
+ } else {
+ throw new Exception("nth: index out of range");
+ }
}
function first($seq) {
@@ -177,6 +181,8 @@ $core_ns = array(
'false?'=> function ($a) { return _false_Q($a); },
'symbol'=> function () { return call_user_func_array('_symbol', func_get_args()); },
'symbol?'=> function ($a) { return _symbol_Q($a); },
+ 'keyword'=> function () { return call_user_func_array('_keyword', func_get_args()); },
+ 'keyword?'=> function ($a) { return _keyword_Q($a); },
'string?'=> function ($a) { return _string_Q($a); },
'pr-str'=> function () { return call_user_func_array('pr_str', func_get_args()); },
diff --git a/php/env.php b/php/env.php
index 61bedaf..a660d3b 100644
--- a/php/env.php
+++ b/php/env.php
@@ -31,7 +31,7 @@ class Env {
}
}
public function find($key) {
- if (array_key_exists($key, $this->data)) {
+ if (array_key_exists($key->value, $this->data)) {
return $this;
} elseif ($this->outer) {
return $this->outer->find($key);
@@ -40,15 +40,15 @@ class Env {
}
}
public function set($key, $value) {
- $this->data[$key] = $value;
+ $this->data[$key->value] = $value;
return $value;
}
public function get($key) {
$env = $this->find($key);
if (!$env) {
- throw new Exception("'" . $key . "' not found");
+ throw new Exception("'" . $key->value . "' not found");
} else {
- return $env->data[$key];
+ return $env->data[$key->value];
}
}
}
diff --git a/php/printer.php b/php/printer.php
index 3839931..130d31b 100644
--- a/php/printer.php
+++ b/php/printer.php
@@ -23,7 +23,9 @@ function _pr_str($obj, $print_readably=True) {
}
return "{" . implode(" ", $ret) . "}";
} elseif (is_string($obj)) {
- if ($print_readably) {
+ if (strpos($obj, chr(0x7f)) === 0) {
+ return ":".substr($obj,1);
+ } elseif ($print_readably) {
$obj = preg_replace('/"/', '\\"', preg_replace('/\\\\/', '\\\\\\\\', $obj));
return '"' . $obj . '"';
} else {
diff --git a/php/reader.php b/php/reader.php
index 83e0cff..ed9063f 100644
--- a/php/reader.php
+++ b/php/reader.php
@@ -40,6 +40,8 @@ function read_atom($reader) {
$str = substr($token, 1, -1);
$str = preg_replace('/\\\\"/', '"', $str);
return $str;
+ } elseif ($token[0] === ":") {
+ return _keyword(substr($token,1));
} elseif ($token === "nil") {
return NULL;
} elseif ($token === "true") {
diff --git a/php/step3_env.php b/php/step3_env.php
index 6585fe7..a31c298 100644
--- a/php/step3_env.php
+++ b/php/step3_env.php
@@ -14,7 +14,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -46,12 +46,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
return MAL_EVAL($ast[2], $let_env);
default:
@@ -73,10 +73,10 @@ function rep($str) {
return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
}
-$repl_env->set('+', function ($a, $b) { return intval($a + $b,10); });
-$repl_env->set('-', function ($a, $b) { return intval($a - $b,10); });
-$repl_env->set('*', function ($a, $b) { return intval($a * $b,10); });
-$repl_env->set('/', function ($a, $b) { return intval($a / $b,10); });
+$repl_env->set(_symbol('+'), function ($a, $b) { return intval($a + $b,10); });
+$repl_env->set(_symbol('-'), function ($a, $b) { return intval($a - $b,10); });
+$repl_env->set(_symbol('*'), function ($a, $b) { return intval($a * $b,10); });
+$repl_env->set(_symbol('/'), function ($a, $b) { return intval($a / $b,10); });
// repl loop
do {
diff --git a/php/step4_if_fn_do.php b/php/step4_if_fn_do.php
index 1e88c6a..7266261 100644
--- a/php/step4_if_fn_do.php
+++ b/php/step4_if_fn_do.php
@@ -15,7 +15,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -47,12 +47,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
return MAL_EVAL($ast[2], $let_env);
case "do":
@@ -93,7 +93,7 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
// core.mal: defined using the language itself
diff --git a/php/step5_tco.php b/php/step5_tco.php
index 88557b5..a3785cf 100644
--- a/php/step5_tco.php
+++ b/php/step5_tco.php
@@ -15,7 +15,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -49,12 +49,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,7 +105,7 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
// core.mal: defined using the language itself
diff --git a/php/step6_file.php b/php/step6_file.php
index 1e83c28..a27acb8 100644
--- a/php/step6_file.php
+++ b/php/step6_file.php
@@ -15,7 +15,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -49,12 +49,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,16 +105,16 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/php/step7_quote.php b/php/step7_quote.php
index 07d3d2a..37903d0 100644
--- a/php/step7_quote.php
+++ b/php/step7_quote.php
@@ -34,7 +34,7 @@ function quasiquote($ast) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -68,12 +68,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -129,16 +129,16 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/php/step8_macros.php b/php/step8_macros.php
index aacf33e..c7e0175 100644
--- a/php/step8_macros.php
+++ b/php/step8_macros.php
@@ -35,13 +35,13 @@ function quasiquote($ast) {
function is_macro_call($ast, $env) {
return is_pair($ast) &&
_symbol_Q($ast[0]) &&
- $env->find($ast[0]->value) &&
- $env->get($ast[0]->value)->ismacro;
+ $env->find($ast[0]) &&
+ $env->get($ast[0])->ismacro;
}
function macroexpand($ast, $env) {
while (is_macro_call($ast, $env)) {
- $mac = $env->get($ast[0]->value);
+ $mac = $env->get($ast[0]);
$args = array_slice($ast->getArrayCopy(),1);
$ast = $mac->apply($args);
}
@@ -50,7 +50,7 @@ function macroexpand($ast, $env) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -87,12 +87,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,7 +105,7 @@ function MAL_EVAL($ast, $env) {
case "defmacro!":
$func = MAL_EVAL($ast[2], $env);
$func->ismacro = true;
- return $env->set($ast[1]->value, $func);
+ return $env->set($ast[1], $func);
case "macroexpand":
return macroexpand($ast[1], $env);
case "do":
@@ -154,16 +154,16 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/php/stepA_more.php b/php/step9_try.php
index f38656f..0470763 100644
--- a/php/stepA_more.php
+++ b/php/step9_try.php
@@ -35,13 +35,13 @@ function quasiquote($ast) {
function is_macro_call($ast, $env) {
return is_pair($ast) &&
_symbol_Q($ast[0]) &&
- $env->find($ast[0]->value) &&
- $env->get($ast[0]->value)->ismacro;
+ $env->find($ast[0]) &&
+ $env->get($ast[0])->ismacro;
}
function macroexpand($ast, $env) {
while (is_macro_call($ast, $env)) {
- $mac = $env->get($ast[0]->value);
+ $mac = $env->get($ast[0]);
$args = array_slice($ast->getArrayCopy(),1);
$ast = $mac->apply($args);
}
@@ -50,7 +50,7 @@ function macroexpand($ast, $env) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -87,12 +87,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,11 +105,9 @@ function MAL_EVAL($ast, $env) {
case "defmacro!":
$func = MAL_EVAL($ast[2], $env);
$func->ismacro = true;
- return $env->set($ast[1]->value, $func);
+ return $env->set($ast[1], $func);
case "macroexpand":
return macroexpand($ast[1], $env);
- case "php*":
- return eval($ast[1]);
case "try*":
$a1 = $ast[1];
$a2 = $ast[2];
@@ -174,19 +172,18 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
-rep("(def! *host-language* \"php\")");
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)))))))");
@@ -198,7 +195,6 @@ if (count($argv) > 1) {
}
// repl loop
-rep("(println (str \"Mal [\" *host-language* \"]\"))");
do {
try {
$line = mal_readline("user> ");
diff --git a/php/step9_interop.php b/php/stepA_mal.php
index d4a59c7..1dc3b04 100644
--- a/php/step9_interop.php
+++ b/php/stepA_mal.php
@@ -35,13 +35,13 @@ function quasiquote($ast) {
function is_macro_call($ast, $env) {
return is_pair($ast) &&
_symbol_Q($ast[0]) &&
- $env->find($ast[0]->value) &&
- $env->get($ast[0]->value)->ismacro;
+ $env->find($ast[0]) &&
+ $env->get($ast[0])->ismacro;
}
function macroexpand($ast, $env) {
while (is_macro_call($ast, $env)) {
- $mac = $env->get($ast[0]->value);
+ $mac = $env->get($ast[0]);
$args = array_slice($ast->getArrayCopy(),1);
$ast = $mac->apply($args);
}
@@ -50,7 +50,7 @@ function macroexpand($ast, $env) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -87,12 +87,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,11 +105,41 @@ function MAL_EVAL($ast, $env) {
case "defmacro!":
$func = MAL_EVAL($ast[2], $env);
$func->ismacro = true;
- return $env->set($ast[1]->value, $func);
+ return $env->set($ast[1], $func);
case "macroexpand":
return macroexpand($ast[1], $env);
case "php*":
- return eval($ast[1]);
+ $res = eval($ast[1]);
+ switch (gettype($res)) {
+ case "array":
+ if ($res !== array_values($res)) {
+ $new_res = _hash_map();
+ $new_res->exchangeArray($res);
+ return $new_res;
+ } else {
+ return call_user_func_array('_list', $res);
+ }
+ default:
+ return $res;
+ }
+ case "try*":
+ $a1 = $ast[1];
+ $a2 = $ast[2];
+ if ($a2[0]->value === "catch*") {
+ try {
+ return MAL_EVAL($a1, $env);
+ } catch (Error $e) {
+ $catch_env = new Env($env, array($a2[1]),
+ array($e->obj));
+ return MAL_EVAL($a2[2], $catch_env);
+ } catch (Exception $e) {
+ $catch_env = new Env($env, array($a2[1]),
+ array($e->getMessage()));
+ return MAL_EVAL($a2[2], $catch_env);
+ }
+ } else {
+ return MAL_EVAL($a1, $env);
+ }
case "do":
eval_ast($ast->slice(1, -1), $env);
$ast = $ast[count($ast)-1];
@@ -156,18 +186,19 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
+rep("(def! *host-language* \"php\")");
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)))))))");
@@ -179,6 +210,7 @@ if (count($argv) > 1) {
}
// repl loop
+rep("(println (str \"Mal [\" *host-language* \"]\"))");
do {
try {
$line = mal_readline("user> ");
diff --git a/php/tests/stepA_mal.mal b/php/tests/stepA_mal.mal
new file mode 100644
index 0000000..15f8a94
--- /dev/null
+++ b/php/tests/stepA_mal.mal
@@ -0,0 +1,25 @@
+;; Testing basic php interop
+
+(php* "return 7;")
+;=>7
+
+(php* "return '7';")
+;=>"7"
+
+(php* "return array(7,8,9);")
+;=>(7 8 9)
+
+(php* "return array(\"abc\" => 789);")
+;=>{"abc" 789}
+
+(php* "print \"hello\n\";")
+; hello
+;=>nil
+
+(php* "global $foo; $foo=8;")
+(php* "global $foo; return $foo;")
+;=>8
+
+(php* "global $f; $f = function($v) { return 1+$v; };")
+(php* "global $f; return array_map($f, array(1,2,3));")
+;=>(2 3 4)
diff --git a/php/types.php b/php/types.php
index e3df3ac..fa87197 100644
--- a/php/types.php
+++ b/php/types.php
@@ -53,6 +53,13 @@ class SymbolClass {
function _symbol($name) { return new SymbolClass($name); }
function _symbol_Q($obj) { return ($obj instanceof SymbolClass); }
+// Keywords
+function _keyword($name) { return chr(0x7f).$name; }
+function _keyword_Q($obj) {
+ return is_string($obj) && strpos($obj, chr(0x7f)) === 0;
+}
+
+
// Functions
class FunctionClass {
diff --git a/process/guide.md b/process/guide.md
new file mode 100644
index 0000000..ef5e33d
--- /dev/null
+++ b/process/guide.md
@@ -0,0 +1,1211 @@
+# The Make-A-Lisp Process
+
+So you want to write a Lisp interpreter? Welcome!
+
+The goal of the Make-A-Lisp project is to make it easy to write your
+own Lisp interpreter without sacrificing those many "Aha!" moments
+that come from ascending the McCarthy mountain. When you reach the peak
+of this particular mountain, you will have an interpreter for the mal
+Lisp language that is powerful enough to be self-hosting, meaning it
+will be able to run a mal interpreter written in mal itself.
+
+So jump right in (er ... start the climb)!
+
+## Pick a language
+
+You might already have a language in mind that you want to use.
+Technically speaking, mal can be implemented in any sufficiently
+complete programming language (i.e. Turing complete), however, there are a few
+language features that can make the task MUCH easier. Here are some of
+them in rough order of importance:
+
+* A sequential compound data structure (e.g. arrays, lists,
+ vectors, etc)
+* An associative compound data structure (e.g. a dictionary,
+ hash-map, associative array, etc)
+* Function references (first class functions, function pointers,
+ etc)
+* Real exception handling (try/catch, raise, throw, etc)
+* Variable argument functions (variadic, var args, splats, apply, etc)
+* Function closures
+* PCRE regular expressions
+
+In addition, the following will make your task especially easy:
+
+* Dynamic typing / boxed types (specifically, the ability to store
+ different data types in the sequential and associative structures
+ and the language keeps track of the type for you)
+* Compound data types support arbitrary runtime "hidden" data
+ (metadata, metatables, dynamic fields attributes)
+
+Here are some examples of languages that have all of the above
+features: JavaScript, Ruby, Python, Lua, R, Clojure.
+
+Many of the most popular languages already have Mal implementations.
+However, this should not discourage you from creating your own
+implementation in a language that already has one. However, if you go
+this route, I suggest you avoid referring to the existing
+implementations (i.e. "cheating") to maximize your learning experience
+instead of just borrowing mine. On the other hand, if your goal is to
+add new implementations to mal as efficiently as possible, then you
+SHOULD find the most similar target language implementation and refer
+to it frequently.
+
+If you want a fairly long list of programming languages with an
+approximate measure of popularity, try the [Programming Language
+Popularity Chart](http://langpop.corger.nl/)
+
+
+## Getting started
+
+* Install your chosen language interpreter/compiler, language package
+ manager and build tools (if applicable)
+
+* Fork the mal repository on github and then clone your forked
+ repository:
+```
+git clone git@github.com:YOUR_NAME/mal.git
+cd mal
+```
+
+* Make a new directory for your implementation. For example, if your
+language is called "quux":
+```
+mkdir quux
+```
+
+* Modify the top level Makefile to allow the tests to be run against
+ your implementation. For example, if your language is named "quux"
+ and uses "qx" as the file extension, then make the following
+ 3 modifications to Makefile:
+```
+IMPLS = ... quux ...
+...
+quux_STEP_TO_PROG = mylang/$($(1)).qx
+...
+quux_RUNSTEP = ../$(2) $(3)
+```
+
+This allows you to run tests against your implementation like this:
+```
+make test^quux^stepX
+```
+
+
+## General hints
+
+Stackoverflow and Google are your best friends. Modern polyglot
+developers do not memorize dozens of programming languages. Instead,
+they learn the peculiar terminology used with each language and then
+use this to search for their answers.
+
+Here are some other resources where multiple languages are
+compared/described:
+* http://learnxinyminutes.com/
+* http://hyperpolyglot.org/
+* http://rosettacode.org/
+* http://rigaux.org/language-study/syntax-across-languages/
+
+Do not let yourself be bogged down by specific problems. While the
+make-a-lisp process is structured as a series of steps, the reality is
+that building a lisp interpreter is more like a branching tree. If you
+get stuck on tail call optimization, or hash-maps, move on to other
+things. You will often have a stroke of inspiration for a problem as
+you work through other functionality. I have tried to structure this
+guide and the tests to make clear which things are optional or can be
+deferred until later.
+
+An aside on optional bits: when you run the tests for a given step,
+the last tests are often marked with an "optional" header. This
+indicates that these are tests for functionality that is not critical
+to finish a basic mal implementation. Many of the steps in this
+process guide also have an "Optional" section, however, it is not
+quite the same meaning. Those sections do include the functionality
+that is marked as optional in the tests, but they also include
+functionality that becomes mandatory at a later step. In other words,
+this is a "make your own Lisp adventure".
+
+Use test driven development. Each step of the make-a-lisp process has
+a bunch of tests associated with it and there is an easy script to run
+all the tests for a specific step in the process. Pick a failing test,
+fix it, repeat until all the tests for that step pass.
+
+The `process` directory contains abbreviated pseudocode and
+architecture images for each step of the make-a-lisp process. Use
+a textual diff/comparison tool to compare the previous pseudocode step
+with the one you are working on. The architecture images have changes
+from the previous step highlighted in red.
+
+If you get stuck, find the same step or functionality in a different
+implementation language.
+
+
+## The Make-A-Lisp Process
+
+In the steps that follow the name of the target language is "quux" and
+the file extension for that language is "qx".
+
+
+<a name="step0"></a>
+
+### Step 0: The REPL
+
+![step0_repl architecture](step0_repl.png)
+
+This step is basically just creating a skeleton of your interpreter.
+
+* Create a `step0_repl.qx` file in `quux/`.
+
+* Add the 4 trivial functions `READ`, `EVAL`, `PRINT`, and `rep`
+ (read-eval-print). `READ`, `EVAL`, and `PRINT` are basically just
+ stubs that return their first parameter (a string if your target
+ language is a statically typed) and `rep` calls them in order
+ passing the return to the input of the next.
+
+* Add a main loop that repeatedly prints a prompt, gets a line of
+ input from the user, calls `rep` with that line of input, and then
+ prints out the result from `rep`. It should also exit when you send
+ it an EOF (often Ctrl-D).
+
+* If you are using a compiled (ahead-of-time rather than just-in-time)
+ language, then create a Makefile (or appropriate project definition
+ file) in your directory.
+
+Run your new program and make sure that it echos each line that you
+type. Because step0 is so trivial, there are no automated tests to run
+for it.
+
+Add and then commit your new `step0_repl.qx` and `Makefile` to git.
+
+Congratulations! You have just completed the first step of the
+make-a-lisp process.
+
+
+#### Optional:
+
+* Add full line editing and command history support to your
+ interpreter REPL. Many languages have a library/module that provide
+ line editing support. Another option if your language supports it is
+ to use an FFI (foreign function interface) to load and call directly
+ into GNU readline, editline, or libnoise library. Add line
+ editing interface code to `readline.qx`
+
+
+<a name="step1"></a>
+
+### Step 1: Read and Print
+
+![step1_read_print architecture](step1_read_print.png)
+
+In this step, your interpreter will "read" the string from the user
+and parse it into an internal tree data structure (an abstract syntax
+tree) and then take that data structure and "print" it back to
+a string.
+
+In non-lisp languages, this step (called "lexing and parsing") can be
+one of the most complicated parts of the compiler/interpreter. In
+Lisp, the data structure that you want in memory is basically
+represented directly in the code that the programmer writes
+(homoiconicity).
+
+For example, if the string is "(+ 2 (* 3 4))" then the read function
+will process this into a tree structure that looks like this:
+```
+ List
+ / | \
+ / | \
+ / | \
+ Sym:+ Int:2 List
+ / | \
+ / | \
+ / | \
+ Sym:* Int:3 Int:4
+```
+
+Each left paren and its matching right paren (lisp "sexpr") becomes
+a node in the tree and everything else becomes a leaf in the tree.
+
+If you can find code for an implementation of a JSON encoder/decoder
+in your target language then you can probably just borrow and modify
+that and be 75% of the way done with this step.
+
+The rest of this section is going to assume that you are not starting
+from an existing JSON encoder/decoder, but that you do have access to
+a Perl compatible regular expressions (PCRE) module/library. You can
+certainly implement the reader using simple string operations, but it
+is more involved. The `make`, `ps` (postscript) and Haskell
+implementations have examples of a reader/parser without using regular
+expression support.
+
+* Copy `step0_repl.qx` to `step1_read_print.qx`.
+
+* Add a `reader.qx` file to hold functions related to the reader.
+
+* If the target language has objects types (OOP), then the next step
+ is to create a simple stateful Reader object in `reader.qx`. This
+ object will store the tokens and a position. The Reader object will
+ have two methods: `next` and `peek`. `next` returns the tokens at
+ the current position and increments the position. `peek` just
+ returns the token at the current position.
+
+* Add a function `read_str` in `reader.qx`. This function
+ will call `tokenizer` and then create a new Reader object instance
+ with the tokens. Then it will call `read_form` with the Reader
+ instance.
+
+* Add a function `tokenizer` in `reader.qx`. This function will take
+ a single single string and return an array/list
+ of all the tokens (strings) in it. The following regular expression
+ (PCRE) will match all mal tokens.
+```
+[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)
+```
+
+* Add the function `read_form` to `reader.qx`. This function
+ will peek at the first token in the Reader object and switch on the
+ first character of that token. If the character is a left paren then
+ `read_list` is called with the Reader object. Otherwise, `read_atom`
+ is called with the Reader Object. The return value from `read_form`
+ is a mal data type. If your target language is statically typed then
+ you will need some way for `read_form` to return a variant or
+ subclass type. For example, if your language is object oriented,
+ then you cal define a top level MalType (in `types.qx`) that all
+ your mal data types inherit from. The MalList type (which also
+ inherits from MalType) will contains a list/array of other MalTypes.
+ If your language is dynamically typed then you can likely just
+ return a plain list/array of other mal types.
+
+* Add the function `read_list` to `reader.qx`. This function will
+ repeatedly call `read_form` with the Reader object until it
+ encounters a ')' token (if it reach EOF before reading a ')' then
+ that is an error). It accumulates the results into a List type. If
+ your language does not have a sequential data type that can hold mal
+ type values you may need to implement one (in `types.qx`). Note
+ that `read_list` repeatedly calls `read_form` rather than
+ `read_atom`. This mutually recursive defintion between `read_list`
+ and `read_form` is what allows lists to contain lists.
+
+* Add the function `read_atom` to `reader.qx`. This function will
+ look at the contents of the token and return the appropriate scalar
+ (simple/single) data type value. Initially, you can just implement
+ numbers (integers) and symbols . This will allow you to proceed
+ through the next couple of steps before you will need to implement
+ the other fundamental mal types: nil, true, false, and string. The
+ remaining mal types: keyword, vector, hash-map, and atom do not
+ need to be implemented until step 9 (but can be implemented at any
+ point between this step and that). BTW, symbols types are just an
+ object that contains a single string name value (some languages have
+ symbol types already).
+
+* Add a file `printer.qx`. This file will contain a single function
+ `pr_str` which does the opposite of `read_str`: take a mal data
+ structure and return a string representation of it. But `pr_str` is
+ much simpler and is basically just a switch statement on the type of
+ the input object:
+
+ * symbol: return the string name of the symbol
+ * number: return the number as a string
+ * list: iterate through each element of the list calling `pr_str` on
+ it, then join the results with a space separator, and surround the
+ final result with parens
+
+* Change the `READ` function in `step1_read_print.qx` to call
+ `reader.read_str` and the `PRINT` function to call `printer.pr_str`.
+ `EVAL` continues to simply return its input but the type is now
+ a mal data type.
+
+You now have enough hooked up to begin testing your code. You can
+manually try some simple inputs:
+ * `123` -> `123`
+ * ` 123 ` -> `123`
+ * `abc` -> `abc`
+ * ` abc ` -> `abc`
+ * `(123 456)` -> `(123 456)`
+ * `( 123 456 789 ) ` -> `(123 456 789)`
+ * `( + 2 (* 3 4) ) ` -> `(+ 2 (* 3 4))`
+
+To verify that your code is doing more than just eliminating extra
+spaces (and not failing), you can instrument your `reader.qx` functions.
+
+Once you have gotten past those simple manual tests, it is time to run
+the full suite of step 1 tests. Go to the top level and run the
+following:
+```
+make test^quux^step1
+```
+
+Fix any test failures related to symbols, numbers and lists.
+
+Depending on the functionality of your target language, it is likely
+that you have now just completed one of the most difficult steps. It
+is down hill from here. The remaining steps will probably be easier
+and each step will give progressively more bang for the buck.
+
+#### Optional:
+
+
+* Add error checking to your reader functions to make sure parens
+ are properly matched. Catch and print these errors in your main
+ loop. If your language does not have try/catch style bubble up
+ exception handling, then you will need to add explicit error
+ handling to your code to catch and pass on errors without crashing.
+
+* Add support for the other basic data type to your reader and printer
+ functions: string, nil, true, and false. These become mandatory at
+ step 4. When a string is read, a slash followed by a doublequote is
+ translated into a plain doublequote character and a slash followed by
+ "n" is translated into a newline. To properly print a string (for
+ step 4 string functions), the `pr_str` function needs another
+ parameter called `print_readably`. When `print_readably` is true,
+ doublequotes and newlines are translated into their printed
+ representations (the reverse of the reader). The `PRINT` function in
+ the main program should call `pr_str` with print_readably set to
+ 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/distringuishing from a list.
+ * hash-map: only need to implement string keys (which enables
+ keyword keys since they are just special strings).
+
+* Add support for reader macros which are special forms that are
+ transformed into other forms during the read phase.
+
+* Add comment support to your reader. The tokenizer should ignore
+ tokens that start with ";". Your `read_str` function will need to
+ properly handle when the tokenizer returns no values. The simplest
+ way to do this is to return `nil` mal value. A cleaner option (that
+ does not print `nil` at the prompt is to throw a special exception
+ that causes the main loop to simply continue at the beginning of the
+ loop without calling `rep`.
+
+
+<a name="step2"></a>
+
+### Step 2: Eval
+
+![step2_eval architecture](step2_eval.png)
+
+In step 1 your mal interpreter was basically just a way to validate
+input and eliminate extraneous white space. In this step you will turn
+your interpreter into a simple number calculator by adding
+functionality to the evaluator (`EVAL`).
+
+Compare the pseudocode for step 1 and step 2 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step1_read_print.txt ../process/step2_eval.txt
+```
+
+* Copy `step1_read_print.qx` to `step2_eval.qx`.
+
+* Define a simple initial REPL environment. This environment is an
+ associative structure that maps symbols (or symbol names) to
+ numeric functions. For example, in python this would look something
+ like this:
+```
+repl_env = {'+': lambda a,b: a+b,
+ '-': lambda a,b: a-b,
+ '*': lambda a,b: a*b,
+ '/': lambda a,b: int(a/b)}
+```
+
+* Modify the `rep` function to pass the REPL environment as the second
+ parameter for the `EVAL` call.
+
+* Create a new function `eval_ast` which takes `ast` (mal data type)
+ and an associative structure (the environment from above).
+ `eval_ast` switches on the type of `ast` as follows:
+
+ * symbol: lookup the symbol in the environment structure and return
+ the value or raise an error no value is found
+ * list: return a new list that is the result of calling `EVAL` on
+ each of the members of the list
+ * otherwise just return the original `ast` value
+
+* Modify `EVAL` to check if the first parameter `ast` is a list.
+ * `ast` is not a list: then return the result of calling `eval_ast`
+ on it.
+ * `ast` is a list: call `eval_ast` to get a new evaluated list. Take
+ the first item of the evaluated list and call it as function using
+ the rest of the evaluated list as its arguments.
+
+If your target language does not have full variable length argument
+support (e.g. variadic, vararg, splats, apply) then you will need to
+pass the full list of arguments as a single parameter and split apart
+the individual values inside of every mal function. This is annoying,
+but workable.
+
+The process of taking a list and invoking or executing it to return
+something new is known in Lisp as the "apply" phase.
+
+Try some simple expressions:
+
+ * `(+ 2 3)` -> `5`
+ * `(+ 2 (* 3 4))` -> `14`
+
+The most likely challenge you will encounter is how to properly call
+a function references using an arguments list.
+
+Now go to the top level, run the step 2 tests and fix the errors.
+```
+make test^quux^step2
+```
+
+You now have a simple prefix notation calculator!
+
+
+<a name="step3"></a>
+
+### Step 3: Environments
+
+![step3_env architecture](step3_env.png)
+
+In step 2 you were already introduced to REPL environment (`repl_env`)
+where the basic numeric functions were stored and looked up. In this
+step you will add the ability to create new environments (`let*`) and
+modify exiting environments (`def!`).
+
+A Lisp environment is an associative data structure that maps symbols (the
+keys) to values. But Lisp environments have an additional important
+function: they can refer to another environment (the outer
+environment). During environment lookups, if the current environment
+does not have the symbol, the lookup continues in the outer
+environment, and continues this way until the symbol is either found,
+or the outer environment is `nil` (the outermost environment in the
+chain).
+
+Compare the pseudocode for step 2 and step 3 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step2_eval.txt ../process/step3_env.txt
+```
+
+* Copy `step2_eval.qx` to `step3_env.qx`.
+
+* Create `env.qx` to hold the environment definition.
+
+* Define an `Env` object that is instantiated with a single `outer`
+ parameter and starts with an empty associative data structure
+ property `data`.
+
+* Define three methods for the Env object:
+ * set: takes a symbol key and a mal value and adds to the `data`
+ structure
+ * find: takes a symbol key and if the current environment contains
+ that key then return the environment. If no key is found and outer
+ is not `nil` then call find (recurse) on the outer environment.
+ * get: takes a symbol key and uses the `find` method to locate the
+ environment with the key, then returns the matching value. If no
+ key is found up the outer chain, then throws/raises a "not found"
+ error.
+
+* Update `step2_env.qx` to use the new `Env` type to create the
+ repl_env (with a `nil` outer value) and use the `set` method to add
+ the numeric functions.
+
+* Modify `eval_ast` to call the `get` method on the `env` parameter.
+
+* Modify the apply section of `EVAL` to switch on the first element of
+ the list:
+ * symbol "def!": call the set method of the current environment
+ (second parameter of `EVAL` called `env`) using the unevaluated
+ first parameter (second list element) as the symbol key and the
+ evaluated second parameter as the value.
+ * symbol "let*": create a new environment using the current
+ environment as the outer value and then use the first parameter as
+ a list of new bindings in the "let" environment. Take the second
+ element of the binding list, call `EVAL` using the new "let*"
+ environment as the evaluation environment, then call `set` on the
+ "let" environment using the first binding list element as the key
+ and the evaluated second element as the value. This is repeated
+ for each odd/even pair in the binding list. Note in particular,
+ the bindings earlier in the list can be referred to by later
+ bindings. Finally, the second parameter (third element) of the
+ original `let*` form is evaluated using the new "let*" environment
+ and the result is returned as the result of the `let*` (the new
+ let environment is discarded upon completion).
+ * otherwise: call `eval_ast` on the list and apply the first element
+ to the rest as before.
+
+`def!` and `let*` are Lisp "specials" (or "special atoms") which means
+that they are language level features and more specifically that the
+rest of the list elements (arguments) may be evaluated differently (or
+not at all) unlike the default apply case where all elements of the
+list are evaluated before the first element is invoked. Lists which
+contain a "special" as the first element are known as "special forms".
+The are special because the follow special evaluation rules.
+
+Try some simple environment tests:
+
+ * `(def! a 6)` -> `6`
+ * `a` -> `6`
+ * `(def! b (+ a 2))` -> `8`
+ * `(+ a b)` -> `14`
+ * `(let* (c 2) c)` -> `2`
+
+Now go to the top level, run the step 3 tests and fix the errors.
+```
+make test^quux^step3
+```
+
+You mal implementation is still basically just a numeric calculator
+with save/restore capability. But you have set the foundation for step
+4 where it will begin to feel like a real programming language.
+
+
+An aside on mutation and typing:
+
+The "!" suffix on symbols is used to indicate that this symbol refers
+to a function that mutates something else. In this case, the `def!`
+symbol indicates a special form that will mutate the current
+environment. Many (maybe even most) of runtime problems that are
+encountered in software engineering are a result of mutation. By
+clearly marking code where mutation may occur, you can more easily
+track down the likely cause of runtime problems when they do occur.
+
+Another cause of runtime errors is type errors, where a value of one
+type is unexpectedly treated by the program as a different and
+incompatible type. Statically typed languages try to make the
+programmer solve all type problems before the program is allowed to
+run. Most Lisp variants tend to be dynamically typed (types of values
+are checked when they are actually used at runtime).
+
+As an aside-aside: The great debate between static and dynamic typing
+debate can be understood by following the money. Advocates of strict
+static typing use words like "correctness" and "safety" and thus get
+government and academic funding. Advocates of dynamic typing use words
+like "agile" and "time-to-market" and thus get venture capital and
+commercial funding.
+
+
+<a name="step4"></a>
+
+### Step 4: If Fn Do
+
+![step4_if_fn_do architecture](step4_if_fn_do.png)
+
+In step 3 you added environments and the special forms for
+manipulating environments. In this step you will add 3 new special
+forms (`if`, `fn*` and `do`) and add several more core functions to
+the default REPL environment. Our new architecture will look like
+this:
+
+The `fn*` special form is how new user-defined functions are created.
+In some Lisps, this special form is named "lambda".
+
+Compare the pseudocode for step 3 and step 4 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step3_env.txt ../process/step4_if_fn_do.txt
+```
+
+* Copy `step3_env.qx` to `step4_if_fn_do.qx`.
+
+* If you have not implemented reader and printer support (and data
+ types) for `nil`, `true` and `false`, you will need to do so for
+ this step.
+
+* Update the constructor/initializer for environments to take two new
+ arguments: `binds` and `exprs`. Bind (`set`) each element (symbol)
+ of the binds list to the respective element of the `exprs` list.
+
+* Add support to `printer.qx` to print functions values. A string
+ literal like "#<function>" is sufficient.
+
+* Add the following special forms to `EVAL`.
+
+ * `do`: Evaluate the all the elements of the list and return the
+ final element (evaluated).
+ * `if`: Evaluate the first parameter (second element). If the result
+ (condition) is anything other than `nil` or `false`, then evaluate
+ the second parammeter (third element of the list) and return the
+ result. Otherwise, evaluate the third parameter (fourth element)
+ and return the result. If condition is false and there is no third
+ parameter, then just return `nil`.
+ * `fn*`: Return a new function closure. The body of that closure
+ does the following:
+ * Create a new environment using `env` (closed over from outer
+ scope) as the `outer` parameter, the first parameter (second
+ list element of `ast` from the outer scope) as the `binds`
+ parameter, and the parameters to the closure as the `exprs`
+ parameter.
+ * Call `EVAL` on the second parameter (third list element of `ast`
+ from outer scope), using the new environment. Use the result as
+ the return value of the closure.
+
+If your target language does not support closures, then you will need
+to implement `fn*` using some sort of structure or object that stores
+the values being closed over: the first and second elements of the
+`ast` list (function parameter list and function body) and the current
+environment `env`. In this case, your native functions will need to be
+wrapped in the same way. You will probably also need a method/function
+that invokes your function object/structure for the default case of
+the apply section of `EVAL`.
+
+Try out the basic functionality you have implemented:
+
+ * `(fn* [a] a)` -> `#<function>`
+ * `( (fn* [a] a) 7)` -> `7`
+ * `( (fn* [a] (+ a 1)) 10)` -> `11`
+ * `( (fn* [a b] (+ a b)) 2 3)` -> `5`
+
+* Add a new file `core.qx` and define an associative data structure
+ `ns` (namespace) that maps symbols to functions. Move the numeric
+ function definitions into this structure.
+
+* Modify `step4_if_fn_do.qx` to iterate through the `core.ns`
+ structure and add (`set`) each symbol/function mapping to the
+ REPL environment (`repl_env`).
+
+* Add the following functions to `core.ns`:
+ * `list`: take the parameters and return them as a list.
+ * `list?`: return true if the first parameter is a list, false
+ otherwise.
+ * `empty?`: treat the first parameter as a list and return true if
+ the list is empty and false if it contains any elements.
+ * `count`: treat the first parameter as a list and return the number
+ of elements that it contains.
+ * `=`: compare the first two parameters and return true if they are
+ the same type and contain the same value. In the case of equal
+ length lists, each element of the list should be compared for
+ equality and if they are the same return true, otherwise false.
+ * `<`, `<=`, `>`, and `>=`: treat the first two parameters as
+ numbers and do the corresponding numeric comparison, returning
+ either true or false.
+
+Now go to the top level, run the step 4 tests. There are a lot of
+tests in step 4 but all of the non-optional tests that do not involve
+strings should be able to pass now.
+
+```
+make test^quux^step4
+```
+
+Your mal implementation is already beginning to look like a real
+language. You have flow control, conditionals, user-defined functions
+with lexical scope, side-effects (if you implement the string
+functions), etc. However, our little interpreter has not quite reach
+Lisp-ness yet. The next several steps will take
+
+#### Optional:
+
+* Implement Clojure-style variadic function parameters. Modify the
+ constructor/initializer for environments, so that if a "&" symbol is
+ encountered in the `binds` list, the next symbol in the `binds` list
+ after the "&" is bound to the rest of the `exprs` list that has not
+ been bound yet.
+
+* Defines a `not` function using mal itself. In `step4_if_fn_do.qx`
+ call the `rep` function with this string:
+ "(def! not (fn* (a) (if a false true)))".
+
+* Implement the strings functions in `core.qx`. To implement these
+ functions, you will need to implement the string support in the
+ reader and printer (optional section of step 1). Each of the string
+ functions takes multiple mal values, prints them (`pr_str`) and
+ joins them together into a new string.
+ * `pr-str`: calls `pr_str` on each argument with `print_readably`
+ set to true, joins the results with " " and returns the new
+ string.
+ * `str`: calls `pr_str` on each argument with `print_readably` set
+ to false, concatenates the results together ("" separator), and
+ returns the new string.
+ * `prn`: calls `pr_str` on each argument with `print_readably` set
+ to true, joins the results with " ", prints the string to the
+ screen and then returns `nil`.
+ * `println`: calls `pr_str` on each argument with `print_readably` set
+ to false, joins the results with " ", prints the string to the
+ screen and then returns `nil`.
+
+
+<a name="step5"></a>
+
+### Step 5: Tail call optimization
+
+![step5_tco architecture](step5_tco.png)
+
+In step 4 you added special forms `do`, `if` and `fn*` and you defined
+some core functions. In this step you will add a Lisp feature called
+tail call optimization (TCO). Also called "tail recursion" or
+sometimes just "tail calls".
+
+Several of the special forms that you have defined in `EVAL` end up
+calling back into `EVAL`. For those forms that call `EVAL` as the last
+thing that they do before returning (tail call) you will just loop back
+to the beginning of eval rather than calling it again. The advantage
+of this approach is that it avoids adding more frames to the call
+stack. This is especially important in Lisp languages because they do
+not tend to have iteration control structures preferring recursion
+instead. However, with tail call optimization, recursion can be made
+as stack efficient as iteration.
+
+Compare the pseudocode for step 4 and step 5 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step4_if_fn_do.txt ../process/step5_tco.txt
+```
+
+* Copy `step4_env.qx` to `step5_tco.qx`.
+
+* Add a loop (e.g. while true) around all code in `EVAL`.
+
+* Modify each of the following form cases to add tail call recursion
+ support:
+ * `let*`: remove the final `EVAL` call on the second `ast` argument
+ (third list element). Set `env` (i.e. the local variable passed in
+ as second parameter of `EVAL`) to the new let environment. Set
+ `ast` (i.e. the local variable passed in as first parameter of
+ `EVAL`) to be the second `ast` argument. Continue at the beginning
+ of the loop (no return).
+ * `do`: change the `eval_ast` call to evaluate all the parameters
+ the except for the last (2nd list element up to but not
+ including last). Set `ast` to the last element of `ast`. Continue
+ at the beginning of the loop (`env` stays unchanged).
+ * `if`: the condition continues to be evaluated, however, rather
+ than evaluating the true or false branch, `ast` is set to the
+ unevaluated value of the chosen branch. Continue at the beginning
+ of the loop (`env` is unchanged).
+
+* The return value from the `fn*` special form will now become an
+ object/structure with attributes that allow the default invoke case
+ of `EVAL` to do TCO on mal functions. Those attributes are:
+ * `fn`: the original function value return in step 4
+ * `ast`: the second `ast` argument (third list element) representing
+ the body of the function.
+ * `params`: the first `ast` argument (second list element)
+ representing the parameter names of the function.
+ * `env`: the current value of the `env` parameter of `EVAL`.
+
+* The default "apply"/invoke case of `EVAL` must now be changed to
+ account for the new object/structure returned by the `fn*` form.
+ Continue to call `eval_ast` on `ast`. The first element is `f`.
+ Switch on the type of `f`:
+ * regular function (not one defined by `fn*`): apply/invoke it as
+ * before (in step 4).
+ * a `fn*` value: set `ast` to the `ast` attribute of `f`. Generate
+ a new environment using the `env` and `params` attributes of `f`
+ as the `outer` and `binds` arguments and rest `ast` arguments
+ (list elements 2 through the end) as the `exprs` argument. Set
+ `env` to the new environment. Continue at the beginning of the loop.
+
+Run some manual tests from previous steps to make sure you have not
+broken anything by adding TCO.
+
+Now go to the top level, run the step 5 tests.
+
+```
+make test^quux^step5
+```
+
+Look at the step 5 test file `tests/step5_tco.mal`. The `sum-to`
+function cannot be tail call optimized because it does something after
+the recursive call (`sum-to` calls itself and then does the addition).
+Lispers say that the `sum-to` is not in tail position. The `sum2`
+function however, calls itself from tail position. In other words, the
+recursive call to `sum2` is the last action that `sum2` does. Calling
+`sum-to` with a large value will cause a stack overflow exception in
+most target languages (some have super-special tricks they use to
+avoid stack overflows).
+
+Congratulations, your mal implementation already has a feature (TCO)
+that most mainstream languages lack.
+
+
+<a name="step6"></a>
+
+### Step 6: Files and Evil
+
+![step6_file architecture](step6_file.png)
+
+In step 5 you added tail call optimization. In this step you will add
+some string and file operations and give your implementation a touch
+of evil ... er, eval. And as long as your language supports function
+closures, this step will be quite simple. However, to complete this
+step, you must implement string type support, so if you have been
+holding off on that you will need to go back and do so.
+
+Compare the pseudocode for step 5 and step 6 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step5_tco.txt ../process/step6_file.txt
+```
+
+* Copy `step5_tco.qx` to `step6_file.qx`.
+
+* Add two new string functions to the core namespaces:
+ * `read-string`: this function just exposes the `read_str` function
+ from the reader. If your mal string type is not the same as your
+ target language (e.g. statically typed language) then your
+ `read-string` function will need to unbox (extract) the raw string
+ from the mal string type in order to call `read_str`.
+ * `slurp`: this function takes a file name (string) and returns the
+ contents of the file as a string. Once again, if your mal string
+ type wraps a raw target language string, then you will need to
+ unmarshall (extract) the string parameter to get the raw file name
+ string and marshall (wrap) the result back to a mal string type.
+
+* In your main program, add a new `eval` (symbol) entry to your REPL
+ environment. The value of the new entry is a regular function
+ closure with a single argument `ast`. The closure calls the real
+ `EVAL` function using the `ast` as the first argument and the REPL
+ environment (closed over from outside) as the second argument. The
+ result of the `EVAL` call is returned.
+
+* Define a `load-file` function using mal itself. In your main
+ program call the `rep` function with this string:
+ "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))".
+
+Try out `load-file`:
+ * `(load-file "../tests/incA.mal")` -> `9`
+ * `(inc4 3)` -> `7`
+
+The `load-file` function does the following:
+ * Call `slurp` to read in a file by name. Surround the contents with
+ "(do ...)" so that the whole file will be treated as a single
+ program AST (abstract syntax tree).
+ * Call `read-string` on the string returned from `slurp`. This uses
+ the reader to read/convert the file contents into mal data/AST.
+ * Call `eval` (the one in the REPL environment) on the AST returned
+ from `read-string` to "run" it.
+
+Now go to the top level, run the step 6 tests. The optional tests will
+need support from the reader for comments, vectors and hash-maps:
+```
+make test^quux^step6
+```
+
+Congratulations, you now have a full-fledged scripting language that
+can run other mal programs. However, the set of functions that are
+available (from `core.qx`) is fairly limited. The bulk of the
+functions you will add are described in step 9, but you will begin to
+flesh them out over the next few steps to support quoting (step 7) and
+macros (step 8).
+
+
+#### Optional:
+
+* Add the ability to run another mal program from the command line.
+ Prior to the REPL loop, check if your mal implementation is called
+ with command line arguments. If so, treat the first argument as
+ a filename and use `rep` to call `load-file` on that filename, and
+ finally exit/terminate execution.
+
+* Add the rest of the command line arguments to your REPL environment
+ so that programs that are run with `load-file` have access to their
+ calling environmnet. Add a new "*ARGV*" (symbol) entry to your REPL
+ environment. The value of this entry should be the rest of the
+ command line arguments as a mal list value.
+
+
+<a name="step7"></a>
+
+### Step 7: Quoting
+
+![step7_quote architecture](step7_quote.png)
+
+In step 7 you will add the special forms `quote` and `quasiquote` and
+add supporting core functions `cons` and `concat`. The two quote forms
+add a powerful abstraction for manipulating mal code itself
+(meta-programming).
+
+The `quote` special form indicates to the evaluator (`EVAL`) that the
+parameter should not be evaluated (yet). At first glance, this might
+not seem particular useful but an example of what this enables is the
+ability for a mal program to refer to a symbol itself rather than the
+value that it evaluates to. Likewise with lists. For example, consider
+the following:
+
+* `(prn abc)`: this will lookup the symbol `abc` in the current
+ evaluation environment and print it. This will result in error if
+ `abc` is not defined.
+* `(prn (quote abc))`: this will print "abc" (prints the symbol
+ itself). This will work regardless of whether `abc` is defined in
+ the current environment.
+* `(prn (1 2 3))`: this will result in an error because `1` is not
+ a function and cannot be applied to the arguments `(2 3)`.
+* `(prn (quote (1 2 3)))`: this will print "(1 2 3)".
+* `(def! l (quote (1 2 3)))`: list quoting allows us to define lists
+ directly in the code (list literal). Another way of doing this is
+ with the list function: `(def! l (list 1 2 3))`.
+
+The second special quoting form is `quasiquote`. This allows a quoted
+list to have internal elements of the list that are temporarily
+unquoted (normal evaluation). There are two special forms that only
+mean something within a quasiquoted list: `unquote` and
+`splice-unquote`. These are perhaps best explained with some examples:
+
+* `(def! lst (quote (2 3)))` -> `(2 3)`
+* `(quasiquote (1 (unquote lst)))` -> `(1 (2 3))`
+* `(quasiquote (1 (splice-unquote lst)))` -> `(1 2 3)`
+
+The `unquote` form turns evaluation back on for its argument and the
+result of evaluation is put in place into the quasiquoted list. The
+`splice-unquote` also turns evaluation back on for its argument, but
+the evaluated value must be a list which is then "spliced" into the
+quasiquoted list. The true power of the quasiquote form will be
+manifest when it used together with macros (in the next step).
+
+Compare the pseudocode for step 6 and step 7 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step6_file.txt ../process/step7_quote.txt
+```
+
+* Copy `step6_file.qx` to `step7_quote.qx`.
+
+* Before implementing the quoting forms, you will need to implement
+* some supporting functions in the core namespace:
+ * `cons`: this function takes a list as its second
+ parameter and returns a new list that has the first argument
+ prepended to it.
+ * `concat`: this functions takes 0 or more lists as
+ parameters and returns a new list that is a concatenation of all
+ the list parameters.
+
+An aside on immutability: note that neither cons or concat mutate
+their original list arguments. Any references to them (i.e. other
+lists that they may be "contained" in) will still refer to the
+original unchanged value. Mal, like Clojure, is a language which uses
+immutable data structures. I encourage you to read about the power and
+importance of immutability as implemented in Clojure (from which
+Mal borrows most of its syntax and feature-set).
+
+* Add the `quote` special form. This form just returns its argument
+ (the second list element of `ast`).
+
+* Add the `quasiquote` special form. First implement a helper function
+ `is_pair` that returns true if the parameter is a non-empty list.
+ Then define a `quasiquote` function. This is called from `EVAL` with
+ the first `ast` argument (second list element) and then `ast` is set
+ to the result and execution continues at the top of the loop (TCO).
+ The `quasiquote` function takes a parameter `ast` and has the
+ following conditional:
+ 1. if `is_pair` of `ast` is false: return a new list containing:
+ a symbol named "quote" and `ast`.
+ 2. else if the first element of `ast` is a symbol named "unquote":
+ return the second element of `ast`.
+ 3. if `is_pair` of first element of `ast` is true and the first
+ element of first element of `ast` (`ast[0][0]`) is a symbol named
+ "splice-unquote": return a new list containing: a symbol named
+ "concat", the second element of first element of `ast`
+ (`ast[0][1]`), and the result of calling `quasiquote` with the
+ second through last element of `ast`.
+ 4. otherwise: return a new list containing: a symbol named "cons", the
+ result of calling `quasiquote` on first element of `ast`
+ (`ast[0]`), and result of calling `quasiquote` with the second
+ through last element of `ast`.
+
+
+Now go to the top level, run the step 7 tests:
+```
+make test^quux^step7
+```
+
+Quoting is one of the more mundane functions available in mal, but do
+not let that discourage you. Your mal implementation is almost
+complete, and quoting sets the stage for the next very exiting step:
+macros.
+
+
+#### Optional
+
+* The full names for the quoting forms are fairly verbose. Most Lisp
+ languages have a short-hand syntax and Mal is no exception. These
+ short-hand syntaxes are known as reader macros because they allow us
+ to manipulate mal code during the reader phase. Macros that run
+ during the eval phase are just called "macros" and are described in
+ the next section. Expand the conditional with reader `read_form`
+ function to add the following four cases:
+ * token is "'" (single quote): return a new list that contains the
+ symbol "quote" and the result of reading the next form
+ (`read_form`).
+ * token is "`" (back-tick): return a new list that contains the
+ symbol "quasiquote" and the result of reading the next form
+ (`read_form`).
+ * token is "~" (tilde): return a new list that contains the
+ symbol "unquote" and the result of reading the next form
+ (`read_form`).
+ * token is "~@" (tilde + at sign): return a new list that contains
+ the symbol "splice-unquote" and the result of reading the next
+ form (`read_form`).
+
+* Add support for quoting of vectors. The `is_pair` function should
+ return true if the argument is a non-empty list or vector. `cons`
+ should also accept a vector as the second argument. The return value
+ is a list regardless. `concat` should support concatenation of
+ lists, vectors, or a mix or both. The result is always a list.
+
+
+<a name="step8"></a>
+
+### Step 8: Macros
+
+![step8_macros architecture](step8_macros.png)
+
+Your mal implementation is now ready for one of the most Lispy and
+exciting of all programming concepts: macros. In the previous step,
+quoting enabled some simple manipulation data structures and therefore
+manipulation of mal code (because the `eval` function from step
+6 turns mal data into code). In this step you will be able to mark mal
+functions as macros which can manipulate mal code before it is
+evaluated. In other words, macros are user-defined special forms. Or
+to look at it another way, macros allow mal programs to redefine
+the mal language itself.
+
+Compare the pseudocode for step 7 and step 8 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step7_quote.txt ../process/step8_macros.txt
+```
+
+* Copy `step7_quote.qx` to `step8_macros.qx`.
+
+
+You might think that the infinite power of macros would require some
+sort of complex mechanism, but the implementation is actually fairly
+simple.
+
+* Add a new attribute `is_macro` to mal function types. This should
+ default to false.
+
+* Add a new special form `defmacro!`. This is very similar to the
+ `def!` form, but before the evaluated value (mal function) is set in
+ the environment, the `is_macro` attribute should be set to true.
+
+* Add a `is_macro_call` function: This function takes arguments `ast`
+ and `env`. It returns true if `ast` is a list that contains a symbol
+ as the first element and that symbol refers to a function in the
+ `env` environment and that function has the `is_macro` attribute set
+ to true. Otherwise, it returns false.
+
+* Add a `macroexpand` function: This function takes arguments `ast`
+ and `env`. It calls `is_macro_call` with `ast` and `env` and loops
+ while that condition is true. Inside the loop, the first element of
+ the `ast` list (a symbol), is looked up in the environment to get
+ the macro function. This macro function is then called/applied with
+ the rest of the `ast` elements (2nd through the last) as arguments.
+ The return value of the macro call becomes the new value of `ast`.
+ When the loop completes because `ast` no longer represents a macro
+ call, the current value of `ast` is returned.
+
+* In the evaluator (`EVAL`) before the special forms switch (apply
+ section), perform macro expansion by calling the `macroexpand`
+ function with the current value of `ast` and `env`. Set `ast` to the
+ result of that call. If the new value of `ast` is no longer a list
+ after macro expansion, then return `ast`, otherwise continue with
+ the rest of the apply section (special forms switch).
+
+* Add a new special form condition for `macroexpand`. Call the
+ `macroexpand` function using the first `ast` argument (second list
+ element) and `env`. Return the result. This special form allows
+ a mal program to do explicit macro expansion without applying the
+ result (which can be useful for debugging macro expansion).
+
+Now go to the top level, run the step 8 tests:
+```
+make test^quux^step8
+```
+
+There is a reasonably good chance that the macro tests will not pass
+the first time. Although the implementation of macros is fairly
+simple, debugging runtime bugs with macros can be fairly tricky. If
+you do run into subtle problems that are difficult to solve, let me
+recommend a couple of approaches:
+
+* Use the macroexpand special form to eliminate one of the layers of
+ indirection (to expand but skip evaluate). This will often reveal
+ the source of the issue.
+* Add a debug print statement to the top of your main `eval` function
+ (inside the TCO loop) to print the current value of `ast` (hint use
+ `pr_str` to get easier to debug output). Pull up the step8
+ implementation from another language and uncomment its `eval`
+ function (yes, I give you permission to violate the rule this once).
+ Run the two side-by-side. The first difference is likely to point to
+ the bug.
+
+Congratulations! You now have a Lisp interpreter with a super power
+that most non-Lisp languages can only dream of (I have it on good
+authority that languages dream when you are not using them). If you
+are not already familiar with Lisp macros, I suggest the following
+excercise: write a recursive macro that handles postfixed mal code
+(with the function as the last parameter instead of the first). Or
+not. I have not actually done so myself, but I have heard it is an
+interesting excercise.
+
+In the next step you will add try/catch style exception handling to
+your implementation in addition to some new core functions. After
+step9 you will be very close to having a fully self-hosting mal
+implementation. Let us continue!
+
+
+### Optional
+
+* Add the following new core functions which are frequently used in
+ macro functions:
+ * `nth`: this function takes a list (or vector) and a number (index)
+ as arguments, returns the element of the list at the given index.
+ If the index is out of range, this function raises an exception.
+ * `first`: this function takes a list (or vector) as its argument
+ and return the first element. If the list (or vector) is empty or
+ is `nil` then `nil` is returned.
+ * `rest`: this function takes a list (or vector) as its argument and
+ returns a new list containing all the elements except the first.
+
+* In the main program, use the `rep` function to define two new
+ control structures macros. Here are the string arguments for `rep`
+ to define these macros:
+ * `cond`: "(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)))))))"
+ * `or`: "(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))))))))"
+
+
+<a name="step9"></a>
+
+### Step 9: Try
+
+![step9_try architecture](step9_try.png)
+
+Compare the pseudocode for step 8 and step 9 to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step8_macros.txt ../process/step9_try.txt
+```
+
+* Copy `step8_macros.qx` to `step9_try.qx`.
+
+* TODO/TBD
+
+
+<a name="step9"></a>
+
+### Step A: Interop and Self-hosting
+
+![stepA_mal architecture](stepA_mal.png)
+
+Compare the pseudocode for step 9 and step A to get a basic idea of
+the changes that will be made during this step:
+```
+diff -urp ../process/step9_try.txt ../process/stepA_mal.txt
+```
+
+* Copy `step9_try.qx` to `stepA_mal.qx`.
+
+* TODO/TBD
+
+
+## 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
+ whitespace/commas)
diff --git a/process/step0_repl.gliffy b/process/step0_repl.gliffy
new file mode 100644
index 0000000..a80df43
--- /dev/null
+++ b/process/step0_repl.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":222,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":253},"max":{"x":934,"y":725}},"objects":[{"x":401.5,"y":419.0,"rotation":0.0,"id":218,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":218,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">pass through</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":0.5,"y":324.0,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":344.0,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.2928932188134525,"px":1.1102230246251563E-16}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[36.0,-21.0],[88.78571428571433,-21.0],[88.78571428571433,14.441558772842882],[121.00000000000009,14.441558772842882]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.58455673601285,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":581.0,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":226.50000000000003,"y":253.00000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":253.0,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":253.0,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":303.0,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":303.0,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":253.0,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":253.0,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":325.0,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":697.0,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":253.0,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":836.5,"y":402.0,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-43.55844122715712],[-82.21428571428567,-43.55844122715712],[-82.21428571428567,-79.0],[-35.0,-79.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":220,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3984216145800902,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2}},"textStyles":{"global":{"bold":true,"size":"12px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step0_repl.png b/process/step0_repl.png
new file mode 100644
index 0000000..270f427
--- /dev/null
+++ b/process/step0_repl.png
Binary files differ
diff --git a/process/step0_repl.txt b/process/step0_repl.txt
new file mode 100644
index 0000000..c3c6aa0
--- /dev/null
+++ b/process/step0_repl.txt
@@ -0,0 +1,11 @@
+--- step0_repl ----------------------------------
+READ(str): return str
+
+EVAL(ast,env): return ast
+
+PRINT(exp): return exp
+
+rep(str): return PRINT(EVAL(READ(str),""))
+
+main loop: println(rep(readline("user> ")))
+
diff --git a/process/step1_read_print.gliffy b/process/step1_read_print.gliffy
new file mode 100644
index 0000000..7f97836
--- /dev/null
+++ b/process/step1_read_print.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":224,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":253},"max":{"x":934,"y":725}},"objects":[{"x":401.5,"y":419.0,"rotation":0.0,"id":218,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":218,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">pass through</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":0.5,"y":324.0,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":581.0,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":226.50000000000003,"y":253.00000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":253.0,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":253.0,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":303.0,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":303.0,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":253.0,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":253.0,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":325.0,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":697.0,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":253.0,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":344.0,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":393.0,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":218.5,"y":414.0,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":420.0,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.5,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":836.5,"y":402.0,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"12px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step1_read_print.png b/process/step1_read_print.png
new file mode 100644
index 0000000..29cc1cf
--- /dev/null
+++ b/process/step1_read_print.png
Binary files differ
diff --git a/process/step1_read_print.txt b/process/step1_read_print.txt
new file mode 100644
index 0000000..3a0fd7c
--- /dev/null
+++ b/process/step1_read_print.txt
@@ -0,0 +1,14 @@
+--- step1_read_print ----------------------------
+import reader, printer
+
+READ(str): return reader.read_str(str)
+
+EVAL(ast,env): return ast
+
+PRINT(exp): return printer.pr_str(exp)
+
+rep(str): return PRINT(EVAL(READ(str),""))
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
diff --git a/process/step2_eval.gliffy b/process/step2_eval.gliffy
new file mode 100644
index 0000000..d451ee6
--- /dev/null
+++ b/process/step2_eval.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":220,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":53},"max":{"x":934,"y":725}},"objects":[{"x":836.5,"y":402.0,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":324.0,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":344.0,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":420.0,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":581.0,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":414.0,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":253.00000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":253.0,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":253.0,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":303.0,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":393.0,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":303.0,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.5,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":253.0,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":253.0,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":253.0,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":325.0,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":697.0,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":351.5,"y":60.0,"rotation":0.0,"id":213,"width":150.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":213,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#43;</span></span></p>\n<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">-</span></span></p>\n<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">*</span></span></p>\n<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">/</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":306.5,"y":302.0,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p>\n<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":53.0,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":431.5,"y":561.0,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":306.5,"y":53.0,"rotation":0.0,"id":110,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":111,"width":83.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:rgb(204, 0, 0);text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">REPL Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":303.0,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":303.0,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:rgb(204, 0, 0);text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":303.0,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":421.5,"y":303.0,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:rgb(204, 0, 0);text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"12px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step2_eval.png b/process/step2_eval.png
new file mode 100644
index 0000000..da9c805
--- /dev/null
+++ b/process/step2_eval.png
Binary files differ
diff --git a/process/step2_eval.txt b/process/step2_eval.txt
new file mode 100644
index 0000000..beb5500
--- /dev/null
+++ b/process/step2_eval.txt
@@ -0,0 +1,25 @@
+--- step2_eval ----------------------------------
+import types, reader, printer
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return lookup(env, ast) OR raise "'" + ast + "' not found"
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ if not list?(ast): return eval_ast(ast, env)
+ f, args = eval_ast(ast, env)
+ return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = {'+: add_fn, ...}
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
diff --git a/process/step3_env.gliffy b/process/step3_env.gliffy
new file mode 100644
index 0000000..1734da2
--- /dev/null
+++ b/process/step3_env.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":221,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":32,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":39,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":41,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":43,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":45,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":55,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":47,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:rgb(204, 0, 0);text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":49,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":53,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":51,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":34,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":59,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":57,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.9189873417721519}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[123.0,40.00056818108453],[123.0,-33.33295454594361],[123.0,-106.6664772729718],[123.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6227256644502573,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":351.5,"y":59.5,"rotation":0.0,"id":220,"width":150.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":61,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#43;</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">-</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">*</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">/</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step3_env.png b/process/step3_env.png
new file mode 100644
index 0000000..7d0c3b2
--- /dev/null
+++ b/process/step3_env.png
Binary files differ
diff --git a/process/step3_env.txt b/process/step3_env.txt
new file mode 100644
index 0000000..91beda6
--- /dev/null
+++ b/process/step3_env.txt
@@ -0,0 +1,38 @@
+--- step3_env -----------------------------------
+import types, reader, printer, env
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: let_env = ...; return EVAL(ast[2], let_env)
+ _default_: f, args = eval_ast(ast, env)
+ return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+repl_env.set('+, add_fn)
+ ...
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null)
+ data = hash_map()
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
diff --git a/process/step4_if_fn_do.gliffy b/process/step4_if_fn_do.gliffy
new file mode 100644
index 0000000..dd6b29d
--- /dev/null
+++ b/process/step4_if_fn_do.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":217,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"> </span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.9189873417721519}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[123.0,40.00056818108453],[123.0,-33.33295454594361],[123.0,-106.6664772729718],[123.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6227256644502573,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":99.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println</span></span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);text-decoration:none;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * /</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;font-weight:bold;font-size:10px;\"><span style=\"\">list list?</span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p>\n<p style=\"text-align:left;\"><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">empty? count</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"> </span></span></p>\n<p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:rgb(204, 0, 0);text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step4_if_fn_do.png b/process/step4_if_fn_do.png
new file mode 100644
index 0000000..e3cfc7a
--- /dev/null
+++ b/process/step4_if_fn_do.png
Binary files differ
diff --git a/process/step4_if_fn_do.txt b/process/step4_if_fn_do.txt
new file mode 100644
index 0000000..501ebfc
--- /dev/null
+++ b/process/step4_if_fn_do.txt
@@ -0,0 +1,70 @@
+--- step4_if_fn_do ------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: let_env = ...; return EVAL(ast[2], let_env)
+ 'do: return eval_ast(rest(ast), env)[-1]
+ 'if: return EVAL(EVAL(ast[1], env) ? ast[2] : ast[3], env)
+ 'fn*: return (...a) -> EVAL(ast[2], new Env(env, ast[1], a))
+ _default_: f, args = eval_ast(ast, env)
+ return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+
+ 'empty?: empty?,
+ 'count: count}
diff --git a/process/step5_tco.gliffy b/process/step5_tco.gliffy
new file mode 100644
index 0000000..2c67590
--- /dev/null
+++ b/process/step5_tco.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":214,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":99.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * /</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">list list?</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">empty? count</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"> </span></p><p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.5,"y":383.8333333333333,"rotation":0.0,"id":158,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-1.3333333333333144],[-76.67669529663686,-1.3333333333333144],[-76.67669529663686,-171.3333333333333],[-106.55339059327378,-171.3333333333333],[-106.55339059327378,-131.3333333333333]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":477.5,"y":352.5,"rotation":0.0,"id":157,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":84,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":130,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[4.0,0.0],[-74.27669529663689,0.0],[-74.27669529663689,-139.99999999999997],[-104.55339059327378,-139.99999999999997],[-104.55339059327378,-99.99999999999997]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":180,"width":30.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5410276646970311,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">TCO</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":472.83333333333326,"y":449.16666666666663,"rotation":0.0,"id":159,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":73,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":124,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[8.666666666666742,-6.666666666666629],[-69.81002862997008,-6.666666666666629],[-69.81002862997008,-236.6666666666666],[-99.88672392660703,-236.6666666666666],[-99.88672392660703,-196.6666666666666]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":464.83333333333326,"y":481.83333333333326,"rotation":0.0,"id":160,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":74,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":126,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[16.666666666666742,-9.333333333333258],[-61.61002862997009,-9.333333333333258],[-61.61002862997009,-269.33333333333326],[-91.88672392660703,-269.33333333333326],[-91.88672392660703,-229.33333333333323]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"> </span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.9189873417721519}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[123.0,40.00056818108453],[123.0,-33.33295454594361],[123.0,-106.6664772729718],[123.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6227256644502573,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step5_tco.png b/process/step5_tco.png
new file mode 100644
index 0000000..d4d48d6
--- /dev/null
+++ b/process/step5_tco.png
Binary files differ
diff --git a/process/step5_tco.txt b/process/step5_tco.txt
new file mode 100644
index 0000000..450a3bc
--- /dev/null
+++ b/process/step5_tco.txt
@@ -0,0 +1,72 @@
+--- step5_tco -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+
+ 'empty?: empty?,
+ 'count: count}
diff --git a/process/step6_file.gliffy b/process/step6_file.gliffy
new file mode 100644
index 0000000..b488a02
--- /dev/null
+++ b/process/step6_file.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":214,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":99.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println </span></span><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">read-string </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">slurp</span></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * /</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">list list?</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">empty? count</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"> </span></p><p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":393.5,"y":63.5,"rotation":0.0,"id":201,"width":56.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":89,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">eval</span></span></p><p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"color:#cc0000;font-weight:bold;font-size:10px;\"><span style=\"\">*ARGV*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.5,"y":383.8333333333333,"rotation":0.0,"id":158,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-1.3333333333333144],[-76.67669529663686,-1.3333333333333144],[-76.67669529663686,-171.3333333333333],[-106.55339059327378,-171.3333333333333],[-106.55339059327378,-131.3333333333333]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":477.5,"y":352.5,"rotation":0.0,"id":157,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":84,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":130,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[4.0,0.0],[-74.27669529663689,0.0],[-74.27669529663689,-139.99999999999997],[-104.55339059327378,-139.99999999999997],[-104.55339059327378,-99.99999999999997]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":180,"width":30.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5410276646970311,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">TCO</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":472.83333333333326,"y":449.16666666666663,"rotation":0.0,"id":159,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":73,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":124,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[8.666666666666742,-6.666666666666629],[-69.81002862997008,-6.666666666666629],[-69.81002862997008,-236.6666666666666],[-99.88672392660703,-236.6666666666666],[-99.88672392660703,-196.6666666666666]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":464.83333333333326,"y":481.83333333333326,"rotation":0.0,"id":160,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":74,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":126,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[16.666666666666742,-9.333333333333258],[-61.61002862997009,-9.333333333333258],[-61.61002862997009,-269.33333333333326],[-91.88672392660703,-269.33333333333326],[-91.88672392660703,-229.33333333333323]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;font-size:10px;\">load-file</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.9189873417721519}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[123.0,40.00056818108453],[123.0,-33.33295454594361],[123.0,-106.6664772729718],[123.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6227256644502573,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step6_file.png b/process/step6_file.png
new file mode 100644
index 0000000..773247e
--- /dev/null
+++ b/process/step6_file.png
Binary files differ
diff --git a/process/step6_file.txt b/process/step6_file.txt
new file mode 100644
index 0000000..b9b9907
--- /dev/null
+++ b/process/step6_file.txt
@@ -0,0 +1,79 @@
+--- step6_file ----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+
+ 'empty?: empty?,
+ 'count: count}
diff --git a/process/step7_quote.gliffy b/process/step7_quote.gliffy
new file mode 100644
index 0000000..c6eaf46
--- /dev/null
+++ b/process/step7_quote.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":214,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":99.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">read-string </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">slurp</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * /</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">list list?</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"color:#cc0000;font-weight:bold;font-size:10px;\"><span style=\"\">cons concat </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">empty? count </span></span></p><p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":393.5,"y":63.5,"rotation":0.0,"id":201,"width":56.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":89,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">eval</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">*ARGV*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.5,"y":383.8333333333333,"rotation":0.0,"id":158,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-1.3333333333333144],[-76.67669529663686,-1.3333333333333144],[-76.67669529663686,-171.3333333333333],[-106.55339059327378,-171.3333333333333],[-106.55339059327378,-131.3333333333333]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":477.5,"y":352.5,"rotation":0.0,"id":157,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":84,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":130,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[4.0,0.0],[-74.27669529663689,0.0],[-74.27669529663689,-139.99999999999997],[-104.55339059327378,-139.99999999999997],[-104.55339059327378,-99.99999999999997]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":180,"width":30.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5410276646970311,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">TCO</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":472.83333333333326,"y":449.16666666666663,"rotation":0.0,"id":159,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":73,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":124,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[8.666666666666742,-6.666666666666629],[-69.81002862997008,-6.666666666666629],[-69.81002862997008,-236.6666666666666],[-99.88672392660703,-236.6666666666666],[-99.88672392660703,-196.6666666666666]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":464.83333333333326,"y":481.83333333333326,"rotation":0.0,"id":160,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":74,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":126,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[16.666666666666742,-9.333333333333258],[-61.61002862997009,-9.333333333333258],[-61.61002862997009,-269.33333333333326],[-91.88672392660703,-269.33333333333326],[-91.88672392660703,-229.33333333333323]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":454.1666666666665,"y":513.8333333333333,"rotation":0.0,"id":161,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":75,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":140,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[27.333333333333485,-11.333333333333258],[-50.9433619633034,-11.333333333333258],[-50.9433619633034,-301.33333333333326],[-81.22005725994029,-301.33333333333326],[-81.22005725994029,-261.33333333333326]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":492.5,"rotation":0.0,"id":140,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":66,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.800000000000001,"y":0.0,"rotation":0.0,"id":141,"width":86.40000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quasiquote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":629.0,"y":462.5,"rotation":0.0,"id":138,"width":62.5,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.2500000000000004,"y":0.0,"rotation":0.0,"id":139,"width":60.00000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">load-file</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.9189873417721519}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[123.0,40.00056818108453],[123.0,-33.33295454594361],[123.0,-106.6664772729718],[123.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6227256644502573,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step7_quote.png b/process/step7_quote.png
new file mode 100644
index 0000000..ecf852f
--- /dev/null
+++ b/process/step7_quote.png
Binary files differ
diff --git a/process/step7_quote.txt b/process/step7_quote.txt
new file mode 100644
index 0000000..58360f2
--- /dev/null
+++ b/process/step7_quote.txt
@@ -0,0 +1,86 @@
+--- step7_quote ---------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'empty?: empty?,
+ 'count: count}
diff --git a/process/step8_macros.gliffy b/process/step8_macros.gliffy
new file mode 100644
index 0000000..5eec811
--- /dev/null
+++ b/process/step8_macros.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":215,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":99.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">read-string </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">slurp</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * /</span></span><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">list list?</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">cons concat <span style=\"color:rgb(204, 0, 0);\">nth </span></span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\"><span style=\"color:rgb(204, 0, 0);\">first rest</span> empty? count </span></span></p><p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":393.5,"y":63.5,"rotation":0.0,"id":201,"width":56.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":89,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">eval</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">*ARGV*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.5,"y":383.8333333333333,"rotation":0.0,"id":158,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-1.3333333333333144],[-76.67669529663686,-1.3333333333333144],[-76.67669529663686,-171.3333333333333],[-106.55339059327378,-171.3333333333333],[-106.55339059327378,-131.3333333333333]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":477.5,"y":352.5,"rotation":0.0,"id":157,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":84,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":130,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[4.0,0.0],[-74.27669529663689,0.0],[-74.27669529663689,-139.99999999999997],[-104.55339059327378,-139.99999999999997],[-104.55339059327378,-99.99999999999997]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":180,"width":30.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5410276646970311,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">TCO</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":136,"py":0.0,"px":0.2928932188134524}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.8227848101265823}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[85.00000000000011,70.02649212270546],[85.00000000000011,-13.315671918196358],[85.0,-96.65783595909818],[85.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6679846702072576,"linePerpValue":20.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":187,"width":83.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.484414172761811,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">macroexpand</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":549.8333333333333,"y":312.5,"rotation":0.0,"id":154,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":78,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[119.66666666666663,30.01053612348437],[119.66666666666663,-79.99473193825781],[119.66666666666663,-79.99473193825781],[119.66666666666663,-190.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":472.83333333333326,"y":449.16666666666663,"rotation":0.0,"id":159,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":73,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":124,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[8.666666666666742,-6.666666666666629],[-69.81002862997008,-6.666666666666629],[-69.81002862997008,-236.6666666666666],[-99.88672392660703,-236.6666666666666],[-99.88672392660703,-196.6666666666666]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":464.83333333333326,"y":481.83333333333326,"rotation":0.0,"id":160,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":74,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":126,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[16.666666666666742,-9.333333333333258],[-61.61002862997009,-9.333333333333258],[-61.61002862997009,-269.33333333333326],[-91.88672392660703,-269.33333333333326],[-91.88672392660703,-229.33333333333323]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":454.1666666666665,"y":513.8333333333333,"rotation":0.0,"id":161,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":75,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":140,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[27.333333333333485,-11.333333333333258],[-50.9433619633034,-11.333333333333258],[-50.9433619633034,-301.33333333333326],[-81.22005725994029,-301.33333333333326],[-81.22005725994029,-261.33333333333326]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":591.5,"y":492.5,"rotation":0.0,"id":142,"width":100.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":68,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0000000000000004,"y":0.0,"rotation":0.0,"id":143,"width":96.00000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">macroexpand</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":492.5,"rotation":0.0,"id":140,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":66,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.800000000000001,"y":0.0,"rotation":0.0,"id":141,"width":86.40000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quasiquote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":629.0,"y":462.5,"rotation":0.0,"id":138,"width":62.5,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.2500000000000004,"y":0.0,"rotation":0.0,"id":139,"width":60.00000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":601.5,"y":372.5,"rotation":0.0,"id":136,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":62,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.8000000000000005,"y":0.0,"rotation":0.0,"id":137,"width":86.4,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">defmacro!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">load-file</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":561.5,"y":63.5,"rotation":0.0,"id":212,"width":77.5,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":93,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;font-size:10px;\">cond</span></p><p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;font-size:10px;\">or</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step8_macros.png b/process/step8_macros.png
new file mode 100644
index 0000000..208f456
--- /dev/null
+++ b/process/step8_macros.png
Binary files differ
diff --git a/process/step8_macros.txt b/process/step8_macros.txt
new file mode 100644
index 0000000..47e5db9
--- /dev/null
+++ b/process/step8_macros.txt
@@ -0,0 +1,100 @@
+--- step8_macros --------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro call
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+
+ ast = macroexpand(ast, env)
+ if not list?(ast): return ast
+
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count}
diff --git a/process/step9_try.gliffy b/process/step9_try.gliffy
new file mode 100644
index 0000000..bc23dbf
--- /dev/null
+++ b/process/step9_try.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":215,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":154.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">throw </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">nil? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">true? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">false? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">symbol symbol? keyword keyword?</span></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\"><span style=\"color:rgb(204, 0, 0);\">readline</span> </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">read-string </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">slurp</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * /</span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"><br /></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">list list? <span style=\"color:rgb(204, 0, 0);\">vector vector? </span></span></span><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">hash-map map? assoc dissoc </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">get contains? keys vals</span></span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\"><span style=\"color:rgb(204, 0, 0);\">sequential?</span> cons concat nth </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">first rest empty? count <span style=\"color:rgb(204, 0, 0);\">apply </span></span></span><span style=\"color:#cc0000;font-weight:bold;font-size:10px;\"><span style=\"\">map conj</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"color:#cc0000;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">with-meta meta atom atom? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">deref reset! swap!</span></span></span></p><p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":393.5,"y":63.5,"rotation":0.0,"id":201,"width":56.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":89,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">eval</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">*ARGV*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.5,"y":383.8333333333333,"rotation":0.0,"id":158,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-1.3333333333333144],[-76.67669529663686,-1.3333333333333144],[-76.67669529663686,-171.3333333333333],[-106.55339059327378,-171.3333333333333],[-106.55339059327378,-131.3333333333333]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":477.5,"y":352.5,"rotation":0.0,"id":157,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":84,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":130,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[4.0,0.0],[-74.27669529663689,0.0],[-74.27669529663689,-139.99999999999997],[-104.55339059327378,-139.99999999999997],[-104.55339059327378,-99.99999999999997]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":180,"width":30.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5410276646970311,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">TCO</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":136,"py":0.0,"px":0.2928932188134524}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.8227848101265823}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[85.00000000000011,70.02649212270546],[85.00000000000011,-13.315671918196358],[85.0,-96.65783595909818],[85.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6679846702072576,"linePerpValue":20.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":187,"width":83.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.484414172761811,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">macroexpand</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":549.8333333333333,"y":312.5,"rotation":0.0,"id":154,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":78,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[119.66666666666663,30.01053612348437],[119.66666666666663,-79.99473193825781],[119.66666666666663,-79.99473193825781],[119.66666666666663,-190.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":472.83333333333326,"y":449.16666666666663,"rotation":0.0,"id":159,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":73,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":124,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[8.666666666666742,-6.666666666666629],[-69.81002862997008,-6.666666666666629],[-69.81002862997008,-236.6666666666666],[-99.88672392660703,-236.6666666666666],[-99.88672392660703,-196.6666666666666]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":464.83333333333326,"y":481.83333333333326,"rotation":0.0,"id":160,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":74,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":126,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[16.666666666666742,-9.333333333333258],[-61.61002862997009,-9.333333333333258],[-61.61002862997009,-269.33333333333326],[-91.88672392660703,-269.33333333333326],[-91.88672392660703,-229.33333333333323]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":454.1666666666665,"y":513.8333333333333,"rotation":0.0,"id":161,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":75,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":140,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[27.333333333333485,-11.333333333333258],[-50.9433619633034,-11.333333333333258],[-50.9433619633034,-301.33333333333326],[-81.22005725994029,-301.33333333333326],[-81.22005725994029,-261.33333333333326]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":591.5,"y":492.5,"rotation":0.0,"id":142,"width":100.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":68,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0000000000000004,"y":0.0,"rotation":0.0,"id":143,"width":96.00000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">macroexpand</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":492.5,"rotation":0.0,"id":140,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":66,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.800000000000001,"y":0.0,"rotation":0.0,"id":141,"width":86.40000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quasiquote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":629.0,"y":462.5,"rotation":0.0,"id":138,"width":62.5,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.2500000000000004,"y":0.0,"rotation":0.0,"id":139,"width":60.00000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":601.5,"y":372.5,"rotation":0.0,"id":136,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":62,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.8000000000000005,"y":0.0,"rotation":0.0,"id":137,"width":86.4,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">defmacro!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">load-file</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":561.5,"y":63.5,"rotation":0.0,"id":212,"width":77.5,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":93,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">cond</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">or</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":664.8333333333334,"y":319.83333333333337,"rotation":0.0,"id":152,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":81,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-88.66666666666674,82.66757369446611],[-88.66666666666674,-39.83287981943363],[-88.66666666666674,-39.83287981943363],[-88.66666666666674,-162.33333333333337]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":481.5,"y":402.5,"rotation":0.0,"id":122,"width":110.00000000000001,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#cc0000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.200000000000001,"y":0.0,"rotation":0.0,"id":123,"width":105.60000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"color:#cc0000;text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">try*/catch*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#cc0000","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2,"stroke":"#cc0000"}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/step9_try.png b/process/step9_try.png
new file mode 100644
index 0000000..401af23
--- /dev/null
+++ b/process/step9_try.png
Binary files differ
diff --git a/process/step9_try.txt b/process/step9_try.txt
new file mode 100644
index 0000000..7fd8fff
--- /dev/null
+++ b/process/step9_try.txt
@@ -0,0 +1,102 @@
+--- step9_try -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro call
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+
+ ast = macroexpand(ast, env)
+ if not list?(ast): return ast
+
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'try*: return ... // try/catch native and malval exceptions
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'throw: throw,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count}
diff --git a/process/step9_try2.txt b/process/step9_try2.txt
new file mode 100644
index 0000000..290fd48
--- /dev/null
+++ b/process/step9_try2.txt
@@ -0,0 +1,133 @@
+--- step9_try -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro call
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+
+ ast = macroexpand(ast, env)
+ if not list?(ast): return ast
+
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'try*: return ... // try/catch native and malval exceptions
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'throw: throw,
+
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol: symbol,
+ 'symbol?: symbol?,
+ 'keyword: keyword,
+ 'keyword?: keyword?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'readline: readline,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'vector: vector,
+ 'vector?: vector?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+ 'assoc: assoc,
+ 'dissoc: dissoc,
+ 'get: get,
+ 'contains?: contains?,
+ 'keys: keys,
+ 'vals: vals,
+
+ 'sequential? sequential?,
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count,
+ 'apply: apply,
+ 'map: map,
+ 'conj: conj,
+
+ 'meta: (a) -> a[0].meta,
+ 'with-meta: (a) -> a[0].with_meta(a[1]),
+ 'atom: (a) -> new Atom(a[0]),
+ 'atom?: (a) -> type(a[0]) == "atom",
+ 'deref: (a) -> a[0].val,
+ 'reset!: (a) -> a[0].val = a[1],
+ 'swap!: swap!}
diff --git a/process/stepA_mal.gliffy b/process/stepA_mal.gliffy
new file mode 100644
index 0000000..7d4920f
--- /dev/null
+++ b/process/stepA_mal.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":934,"height":725,"nodeIndex":215,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":20,"y":18.5},"max":{"x":934,"y":724.5}},"objects":[{"x":264.5,"y":87.5,"rotation":0.0,"id":208,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":91,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.29289321881345237,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.0,-1.4213562373095243],[12.007480555277652,-1.4213562373095243],[27.01496111055536,-1.4213562373095243],[42.022441665833014,-1.4213562373095243]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":31.5,"y":63.5,"rotation":0.0,"id":207,"width":225.0,"height":154.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">throw </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">nil? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">true? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">false? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">symbol symbol? keyword keyword?</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">pr-str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">str </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">prn </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">println </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">readline </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">read-string </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">slurp</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">&lt; &lt;&#61; &gt; &gt;&#61; </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">&#43; - * / </span></span><span style=\"color:#cc0000;font-weight:bold;font-size:10px;\"><span style=\"\">time-ms</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">list list? vector vector? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">hash-map map? assoc dissoc </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">get contains? keys vals</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">sequential? cons concat nth </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">first rest empty? count apply </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">map conj</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">with-meta meta atom atom? </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">deref reset! swap!</span></span></p><p style=\"text-align:left;\"></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":393.5,"y":63.5,"rotation":0.0,"id":201,"width":56.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":89,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">eval</span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;text-decoration:none;font-family:Arial;font-size:10px;\"><span style=\"text-decoration:none;\"> </span></span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">*ARGV*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.5,"y":383.8333333333333,"rotation":0.0,"id":158,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-1.3333333333333144],[-76.67669529663686,-1.3333333333333144],[-76.67669529663686,-171.3333333333333],[-106.55339059327378,-171.3333333333333],[-106.55339059327378,-131.3333333333333]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":477.5,"y":352.5,"rotation":0.0,"id":157,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":84,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":130,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[4.0,0.0],[-74.27669529663689,0.0],[-74.27669529663689,-139.99999999999997],[-104.55339059327378,-139.99999999999997],[-104.55339059327378,-99.99999999999997]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":180,"width":30.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5410276646970311,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">TCO</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":835.5,"y":82.5,"rotation":0.0,"id":58,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":56,"py":0.0,"px":0.9711340206185567}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":0.0,"px":0.9200000000000002}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-120.69072164948443,5.0],[-120.69072164948443,-58.5],[-165.5999999999999,-58.5],[-165.5999999999999,-30.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":168,"width":30.0,"height":11.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6273328731976967,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:10px;\"><span style=\"\">outer</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":546.5,"y":302.5,"rotation":0.0,"id":51,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":79,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":136,"py":0.0,"px":0.2928932188134524}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":44,"py":1.0,"px":0.8227848101265823}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[85.00000000000011,70.02649212270546],[85.00000000000011,-13.315671918196358],[85.0,-96.65783595909818],[85.0,-180.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":189,"width":68.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.6679846702072576,"linePerpValue":20.0,"cardinalityType":null,"html":"<p style=\"text-align:right;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">update env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":301.5,"rotation":0.0,"id":46,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":33,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":44,"py":1.0,"px":0.06329113924050633}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":90,"py":0.0,"px":0.8}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[25.0,-179.0],[25.0,-118.99629641060108],[25.000000000000057,-58.99259282120215],[25.000000000000057,1.0111107681967724]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":112,"width":47.0,"height":28.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3943470868113573,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">symbol</span></span></p><p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">lookup</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":431.5,"y":560.5,"rotation":0.0,"id":42,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":18,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":20,"py":1.0,"px":0.2392857142857143}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-110.0,-28.0],[-110.0,12.0],[57.0,12.0],[57.0,-28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":187,"width":83.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.484414172761811,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">macroexpand</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":302.5,"rotation":0.0,"id":36,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.0,"px":0.26785714285714285}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[15.0,0.0],[15.0,-89.99999999999997],[-108.55339059327378,-89.99999999999997],[-108.55339059327378,-49.99999999999997]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":836.5,"y":401.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":30,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.0,33.5],[0.0,2.6666666666666856],[0.0,-28.166666666666686],[0.0,-59.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":185,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.42162162162162165,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":0.5,"y":323.5,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":22,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[24.0,-2.0],[39.670212364724215,-2.0],[55.34042472944843,-2.0],[71.01063709417264,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":100,"width":16.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.2339895963963344,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">in</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":105.5,"y":343.5,"rotation":0.0,"id":26,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":22,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":24,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.0,-1.0],[1.0,15.666666666666686],[1.0,32.333333333333314],[1.0,49.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":184,"width":38.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.44000000000000006,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">string</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":18,"width":120.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":190,"width":116.0,"height":56.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* symbol</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* list</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* vector</span></span></p><p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">* hash-map</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":670.5,"y":419.5,"rotation":0.0,"id":17,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":20,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[31.0,-2.0],[81.0,-2.0],[81.0,35.5],[131.0,35.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46236810530620487,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":833.5,"y":580.5,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":13,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,32.0],[3.0,128.0],[-727.0,128.0],[-727.0,32.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":218.5,"y":413.5,"rotation":0.0,"id":9,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":24,"py":0.3973684210526316,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":18,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-77.0,49.53289473684208],[-17.0,49.53289473684208],[-17.0,4.0],[43.0,4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":98,"width":29.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.488635066574355,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">AST</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.50000000000003,"y":252.50000000000003,"rotation":0.0,"id":2,"width":499.99999999999994,"height":359.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":51.5,"y":252.5,"rotation":0.0,"id":0,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":781.5,"y":252.5,"rotation":0.0,"id":13,"width":110.0,"height":360.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":71.5,"y":302.5,"rotation":0.0,"id":22,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":13,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":23,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">readline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":71.5,"y":392.5,"rotation":0.0,"id":24,"width":70.0,"height":177.5,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":25,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">read_str</span></span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":302.5,"rotation":0.0,"id":30,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":31,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">printline</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":801.5,"y":435.0,"rotation":0.0,"id":32,"width":70.0,"height":40.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.4000000000000001,"y":0.0,"rotation":0.0,"id":33,"width":67.2,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">pr_str</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":321.5,"y":87.5,"rotation":0.0,"id":56,"width":405.00000000000006,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":8.099999999999998,"y":0.0,"rotation":0.0,"id":57,"width":388.8000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\">ENV</span></p>","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"children":[]}]},{"x":261.5,"y":302.5,"rotation":0.0,"id":90,"width":90.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":3.0,"y":0.0,"rotation":0.0,"id":91,"width":84.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">eval_ast</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":51.5,"y":252.5,"rotation":0.0,"id":92,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":93,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">READ</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":226.5,"y":252.5,"rotation":0.0,"id":94,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":95,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">EVAL</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":781.5,"y":252.5,"rotation":0.0,"id":96,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":97,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">PRINT</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":914.5,"y":324.5,"rotation":0.0,"id":29,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":30,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-43.0,-2.0],[-23.66666666666663,-2.0],[-4.333333333333371,-2.0],[15.0,-2.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":109,"width":24.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689655172413794,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">out</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":654.5,"y":298.5,"rotation":0.0,"id":53,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-132.0,42.00496464680543],[-132.0,-50.49751767659728],[-132.0,-50.49751767659728],[-132.0,-143.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":656.8333333333334,"y":309.83333333333337,"rotation":0.0,"id":150,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":82,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":146,"py":0.0,"px":0.85}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-107.33333333333337,62.66666666666663],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-45.166666666666686],[-107.33333333333337,-153.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":151,"width":64.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.7928902627511594,"linePerpValue":0.0,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">create env</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":664.8333333333334,"y":319.83333333333337,"rotation":0.0,"id":152,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":81,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-88.66666666666674,82.66757369446611],[-88.66666666666674,-39.83287981943363],[-88.66666666666674,-39.83287981943363],[-88.66666666666674,-162.33333333333337]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":549.8333333333333,"y":312.5,"rotation":0.0,"id":154,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":78,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[119.66666666666663,30.01053612348437],[119.66666666666663,-79.99473193825781],[119.66666666666663,-79.99473193825781],[119.66666666666663,-190.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":472.83333333333326,"y":449.16666666666663,"rotation":0.0,"id":159,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":73,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":124,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[8.666666666666742,-6.666666666666629],[-69.81002862997008,-6.666666666666629],[-69.81002862997008,-236.6666666666666],[-99.88672392660703,-236.6666666666666],[-99.88672392660703,-196.6666666666666]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":464.83333333333326,"y":481.83333333333326,"rotation":0.0,"id":160,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":74,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":126,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[16.666666666666742,-9.333333333333258],[-61.61002862997009,-9.333333333333258],[-61.61002862997009,-269.33333333333326],[-91.88672392660703,-269.33333333333326],[-91.88672392660703,-229.33333333333323]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":454.1666666666665,"y":513.8333333333333,"rotation":0.0,"id":161,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":75,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":140,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[27.333333333333485,-11.333333333333258],[-50.9433619633034,-11.333333333333258],[-50.9433619633034,-301.33333333333326],[-81.22005725994029,-301.33333333333326],[-81.22005725994029,-261.33333333333326]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[]},{"x":421.5,"y":696.5,"rotation":0.0,"id":176,"width":70.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.333333333333333,"y":0.0,"rotation":0.0,"id":177,"width":65.33333333333331,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">LOOP</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":372.5,"rotation":0.0,"id":146,"width":80.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":70,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.6000000000000005,"y":0.0,"rotation":0.0,"id":147,"width":76.80000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">&#34;apply&#34;</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":591.5,"y":492.5,"rotation":0.0,"id":142,"width":100.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":68,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0000000000000004,"y":0.0,"rotation":0.0,"id":143,"width":96.00000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">macroexpand</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":492.5,"rotation":0.0,"id":140,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":66,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.800000000000001,"y":0.0,"rotation":0.0,"id":141,"width":86.40000000000003,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quasiquote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":629.0,"y":462.5,"rotation":0.0,"id":138,"width":62.5,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.2500000000000004,"y":0.0,"rotation":0.0,"id":139,"width":60.00000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">quote</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":601.5,"y":372.5,"rotation":0.0,"id":136,"width":90.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":62,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.8000000000000005,"y":0.0,"rotation":0.0,"id":137,"width":86.4,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">defmacro!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":646.5,"y":342.5,"rotation":0.0,"id":134,"width":45.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.9000000000000001,"y":0.0,"rotation":0.0,"id":135,"width":43.199999999999996,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">def!</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":342.5,"rotation":0.0,"id":130,"width":50.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.0000000000000002,"y":0.0,"rotation":0.0,"id":131,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">let*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":656.5,"y":432.5,"rotation":0.0,"id":128,"width":35.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.7000000000000001,"y":0.0,"rotation":0.0,"id":129,"width":33.599999999999994,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">fn*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":462.5,"rotation":0.0,"id":126,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":54,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":127,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"\">if</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":432.5,"rotation":0.0,"id":124,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":0.8000000000000004,"y":0.0,"rotation":0.0,"id":125,"width":38.400000000000006,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">do</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":481.5,"y":402.5,"rotation":0.0,"id":122,"width":110.00000000000001,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.200000000000001,"y":0.0,"rotation":0.0,"id":123,"width":105.60000000000001,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:12px;\"><span style=\"text-decoration:none;\">try*/catch*</span></span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":87,"width":60.00000000000001,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":56.00000000000001,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">apply</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":421.5,"y":302.5,"rotation":0.0,"id":20,"width":280.0,"height":230.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":306.5,"y":52.5,"rotation":0.0,"id":110,"width":60.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":111,"width":55.99999999999999,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Env</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":306.5,"y":52.5,"rotation":0.0,"id":44,"width":395.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":31.5,"y":27.5,"rotation":0.0,"id":195,"width":55.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.833333333333333,"y":0.0,"rotation":0.0,"id":196,"width":51.333333333333314,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:center;\"><span style=\"text-decoration:none;font-weight:bold;font-family:Arial;font-size:14px;\">Core</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":31.5,"y":27.5,"rotation":0.0,"id":197,"width":230.0,"height":200.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":86,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":451.5,"y":63.5,"rotation":0.0,"id":211,"width":100.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"color:rgb(204, 0, 0);font-weight:bold;font-size:10px;\">*host-language*</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">not</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">load-file</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":561.5,"y":63.5,"rotation":0.0,"id":212,"width":77.5,"height":22.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":93,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"<p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">cond</span></p><p style=\"text-align:left;\"><span style=\"font-weight:bold;font-size:10px;\">or</span></p>","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#c9daf8","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"strokeWidth":2,"endArrow":2}},"textStyles":{"global":{"bold":true,"size":"10px","color":"#cc0000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file
diff --git a/process/stepA_mal.png b/process/stepA_mal.png
new file mode 100644
index 0000000..28dcb5b
--- /dev/null
+++ b/process/stepA_mal.png
Binary files differ
diff --git a/process/stepA_mal.txt b/process/stepA_mal.txt
new file mode 100644
index 0000000..6c1e71e
--- /dev/null
+++ b/process/stepA_mal.txt
@@ -0,0 +1,136 @@
+--- stepA_mal -------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro call
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+
+ ast = macroexpand(ast, env)
+ if not list?(ast): return ast
+
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'try*: return ... // try/catch native and malval exceptions
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(f): ast = f.fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! *host-language* \"racket\")")
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+rep("(println (str \"Mal [\" *host-language* \"]\"))")
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'throw: throw,
+
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol: symbol,
+ 'symbol?: symbol?,
+ 'keyword: keyword,
+ 'keyword?: keyword?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'readline: readline,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+ 'time-ms cur-epoch-millis,
+
+ 'list: list,
+ 'list?: list?,
+ 'vector: vector,
+ 'vector?: vector?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+ 'assoc: assoc,
+ 'dissoc: dissoc,
+ 'get: get,
+ 'contains?: contains?,
+ 'keys: keys,
+ 'vals: vals,
+
+ 'sequential? sequential?,
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count,
+ 'apply: apply,
+ 'map: map,
+ 'conj: conj,
+
+ 'meta: (a) -> a[0].meta,
+ 'with-meta: (a) -> a[0].with_meta(a[1]),
+ 'atom: (a) -> new Atom(a[0]),
+ 'atom?: (a) -> type(a[0]) == "atom",
+ 'deref: (a) -> a[0].val,
+ 'reset!: (a) -> a[0].val = a[1],
+ 'swap!: swap!}
diff --git a/ps/Makefile b/ps/Makefile
index 43b5b70..9131674 100644
--- a/ps/Makefile
+++ b/ps/Makefile
@@ -2,7 +2,7 @@
TESTS =
SOURCES_BASE = types.ps reader.ps printer.ps
-SOURCES_LISP = env.ps core.ps stepA_more.ps
+SOURCES_LISP = env.ps core.ps stepA_mal.ps
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
.PHONY: stats tests $(TESTS)
diff --git a/ps/core.ps b/ps/core.ps
index 191e5c3..52c9b05 100644
--- a/ps/core.ps
+++ b/ps/core.ps
@@ -87,8 +87,8 @@ end } def
_list_from_array
end } def
-% [listA listB] -> concat -> [listA... listB...]
-/concat { % replaces matric concat
+% [listA listB] -> do_concat -> [listA... listB...]
+/do_concat {
dup _count 0 eq { %if just concat
pop 0 _list
}{ dup _count 1 eq { %elseif concat of single item
@@ -102,6 +102,15 @@ end } def
} ifelse } ifelse
} def
+% [obj] -> do_count -> number
+/do_count {
+ 0 _nth dup _nil? {
+ pop 0
+ }{
+ _count
+ } ifelse
+} def
+
% [obj ...] -> first -> obj
/first {
0 _nth _first
@@ -220,7 +229,10 @@ end } def
(nil?) { 0 _nth _nil? }
(true?) { 0 _nth _true? }
(false?) { 0 _nth _false? }
+ (symbol) { 0 _nth _symbol }
(symbol?) { 0 _nth _symbol? }
+ (keyword) { 0 _nth _keyword }
+ (keyword?) { 0 _nth _keyword? }
(pr-str) { /data get ( ) true _pr_str_args }
(str) { /data get () false _pr_str_args }
@@ -254,12 +266,12 @@ end } def
(sequential?) { 0 _nth _sequential? }
(cons) { cons }
- (concat) { concat }
+ (concat) { do_concat }
(nth) { dup 0 _nth exch 1 _nth _nth }
(first) { first }
(rest) { rest }
(empty?) { 0 _nth _count 0 eq }
- (count) { 0 _nth _count }
+ (count) { do_count }
(conj) { conj }
(apply) { apply }
(map) { map }
diff --git a/ps/interop.ps b/ps/interop.ps
new file mode 100644
index 0000000..8020ab0
--- /dev/null
+++ b/ps/interop.ps
@@ -0,0 +1,21 @@
+% [ ps_val1...] -> ps2mal -> [ mal_val1...]
+/ps2mal {
+ % convert returned values to Mal types
+ [ exch
+ { %forall returned values
+ dup ==
+ dup type /arraytype eq {
+ (here1\n) print
+ _list_from_array
+ }{ dup type /dicttype eq {
+ (here2\n) print
+ _hash_map_from_dict
+ }{
+ (here3\n) print
+ % no-op
+ } ifelse } ifelse
+ } forall
+ ]
+ (here4\n) print
+} def
+
diff --git a/ps/printer.ps b/ps/printer.ps
index 3062e2d..52d6c1e 100644
--- a/ps/printer.ps
+++ b/ps/printer.ps
@@ -45,13 +45,24 @@
/slen obj 10 add log ceiling cvi def
obj 10 slen string cvrs
}{ /stringtype obj type eq { % if string
- print_readably {
- (")
- obj (\\) (\\\\) replace
- (") (\\") replace
- (") concatenate concatenate
- }{
- obj
+ obj length 0 gt { % if string length > 0
+ obj 0 get 127 eq { %if starts with 0x7f (keyword)
+ obj dup length string copy
+ dup 0 58 put % 58 is ':'
+ }{ print_readably {
+ (")
+ obj (\\) (\\\\) replace
+ (") (\\") replace
+ (") concatenate concatenate
+ }{
+ obj
+ } ifelse } ifelse
+ }{ % else empty string
+ print_readably {
+ ("")
+ }{
+ obj
+ } ifelse
} ifelse
}{ null obj eq { % if nil
(nil)
diff --git a/ps/reader.ps b/ps/reader.ps
index f1f63f6..4b268c0 100644
--- a/ps/reader.ps
+++ b/ps/reader.ps
@@ -52,6 +52,32 @@ end } def
end } def
+% read_keyword: read a single keyword from string/idx
+% string idx -> read_keyword -> name string new_idx
+/read_keyword { 5 dict begin
+ %(in read_keyword\n) print
+ /idx exch def
+ /str exch def
+ /start idx def
+ /cnt 0 def
+ { % loop
+ idx str length ge { exit } if % EOF, break loop
+ /ch str idx 1 getinterval def
+ token_delim ch search { % if token delimeter
+ pop pop pop exit
+ }{ % else not a delim
+ pop
+ /cnt cnt 1 add def
+ } ifelse
+ /idx idx 1 add def % increment idx
+ } loop
+
+ str start cnt getinterval % the matched keyword string
+ dup 0 127 put % TODO: something like (\x029e) would be better
+ str idx % return: keyword string new_idx
+end } def
+
+
% read_string: read a single string from string/idx
% string idx -> read_string -> new_string string new_idx
/read_string { 5 dict begin
@@ -94,8 +120,10 @@ end } def
%ch 48 ge ch 57 le and 45 ch eq or { %if number
ch 48 ge ch 57 le and { %if number
str idx read_number
- }{ ch 34 eq { %elseif double-quote
+ }{ ch 34 eq { %elseif double-quote (string)
str idx read_string
+ }{ ch 58 eq { %elseif colon (keyword)
+ str idx read_keyword
}{
str idx read_symbol
/idx exch def pop
@@ -108,7 +136,7 @@ end } def
}{ %else
str idx % return the original symbol/name
} ifelse } ifelse } ifelse
- } ifelse } ifelse
+ } ifelse } ifelse } ifelse
}ifelse
% return: atom string new_idx
diff --git a/ps/step1_read_print.ps b/ps/step1_read_print.ps
index 476c917..858987c 100644
--- a/ps/step1_read_print.ps
+++ b/ps/step1_read_print.ps
@@ -1,6 +1,7 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step2_eval.ps b/ps/step2_eval.ps
index 551c637..215fc2e 100644
--- a/ps/step2_eval.ps
+++ b/ps/step2_eval.ps
@@ -1,6 +1,7 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step3_env.ps b/ps/step3_env.ps
index 92dc26e..e662c11 100644
--- a/ps/step3_env.ps
+++ b/ps/step3_env.ps
@@ -1,7 +1,8 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step4_if_fn_do.ps b/ps/step4_if_fn_do.ps
index 9e628b6..422f6eb 100644
--- a/ps/step4_if_fn_do.ps
+++ b/ps/step4_if_fn_do.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step5_tco.ps b/ps/step5_tco.ps
index 83fd43b..680c359 100644
--- a/ps/step5_tco.ps
+++ b/ps/step5_tco.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step6_file.ps b/ps/step6_file.ps
index 7d1c876..bc30e35 100644
--- a/ps/step6_file.ps
+++ b/ps/step6_file.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step7_quote.ps b/ps/step7_quote.ps
index d7340fd..3dd9c0c 100644
--- a/ps/step7_quote.ps
+++ b/ps/step7_quote.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step8_macros.ps b/ps/step8_macros.ps
index 3bf304c..32ca3af 100644
--- a/ps/step8_macros.ps
+++ b/ps/step8_macros.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
diff --git a/ps/step9_interop.ps b/ps/step9_try.ps
index de3d2af..d9beec7 100644
--- a/ps/step9_interop.ps
+++ b/ps/step9_try.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
@@ -143,20 +144,6 @@ end } def
env exch a1 exch env_set % def! it
}{ /macroexpand a0 eq { %if defmacro!
ast 1 _nth env macroexpand
- }{ /ps* a0 eq { %if ps*
- count /stackcnt exch def
- ast 1 _nth
- {
- token not { exit } if
- exch
- } loop
- exec
- count stackcnt gt { % if new operands on stack
- % return an list of new operands
- count stackcnt sub array astore
- }{
- null % return nil
- } ifelse
}{ /do a0 eq { %if do
ast _count 2 gt { %if ast has more than 2 elements
ast 1 ast _count 2 sub _slice env eval_ast pop
@@ -164,6 +151,42 @@ end } def
ast ast _count 1 sub _nth % last ast becomes new ast
env
/loop? true def % loop
+ }{ /try* a0 eq { %if try*
+ { %try
+ countdictstack /dictcnt exch def
+ count /stackcnt exch def
+ ast 1 _nth env EVAL
+ } stopped { %catch
+ % clean up the dictionary stack
+ 1 1 countdictstack dictcnt sub { %foreach added dict
+ %(popping dict\n) print
+ pop end % pop idx and pop dict
+ %(new ast: ) print ast true _pr_str print (\n) print
+ } for
+ % clean up the operand stack
+ count 1 exch 1 exch stackcnt sub { %foreach added operand
+ %(op stack: ) print pstack
+ pop pop % pop idx and operand
+ %(popped op stack\n) print pstack
+ } for
+ % get error data and reset $error dict
+ /errdata get_error_data def
+ $error /newerror false put
+ $error /errorinfo null put
+
+ ast _count 3 lt { %if no third (catch*) form
+ errdata throw
+ } if
+ ast 2 _nth 0 _nth (catch*) eq not { %if third form not catch*
+ (No catch* in throw form) _throw
+ } if
+ ast 2 _nth 2 _nth
+ env
+ ast 2 _nth 1 _nth 1 _list
+ errdata 1 _list
+ env_new
+ EVAL
+ } if
}{ /if a0 eq { %if if
/a1 ast 1 _nth def
/cond a1 env EVAL def
diff --git a/ps/stepA_more.ps b/ps/stepA_mal.ps
index 76d0a86..c879294 100644
--- a/ps/stepA_more.ps
+++ b/ps/stepA_mal.ps
@@ -1,8 +1,9 @@
-(types.ps) run
-(reader.ps) run
-(printer.ps) run
-(env.ps) run
-(core.ps) run
+/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
+(types.ps) runlibfile
+(reader.ps) runlibfile
+(printer.ps) runlibfile
+(env.ps) runlibfile
+(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
@@ -149,8 +150,10 @@ end } def
{
token not { exit } if
exch
+ count stackcnt sub 1 roll % send leftover string to bottom
+ exec
+ count stackcnt sub -1 roll % bring leftover string to top
} loop
- exec
count stackcnt gt { % if new operands on stack
% return an list of new operands
count stackcnt sub array astore
diff --git a/ps/tests/stepA_mal.mal b/ps/tests/stepA_mal.mal
new file mode 100644
index 0000000..fffa178
--- /dev/null
+++ b/ps/tests/stepA_mal.mal
@@ -0,0 +1,23 @@
+;; Testing basic ps interop
+
+(ps* "7")
+;=>(7)
+
+(ps* "(7)")
+;=>("7")
+
+(ps* "7 8 9 3 array astore")
+;=>((7 8 9))
+
+(ps* "1 1 eq")
+;=>(true)
+
+(ps* "/sym")
+;=>sym
+
+(ps* "1 1 eq { (yep) }{ (nope) } ifelse")
+;=>("yep")
+
+(ps* "1 0 eq { (yep) }{ (nope) } ifelse")
+;=>("nope")
+
diff --git a/ps/types.ps b/ps/types.ps
index 82be9c2..1f6903e 100644
--- a/ps/types.ps
+++ b/ps/types.ps
@@ -173,11 +173,34 @@ end } def
% Symbols
+/_symbol {
+ dup length string copy cvn
+} def
+
/_symbol? {
type /nametype eq
} def
+% Keywords
+
+/_keyword { 1 dict begin
+ /str exch def
+ str length 1 add string % str2
+ dup 1 str putinterval
+ dup 0 127 put % TODO: something like (\x029e) would be better
+end } def
+
+/_keyword? {
+ dup type /stringtype eq {
+ 0 get 127 eq
+ }{
+ false
+ } ifelse
+} def
+
+
+
% Functions
% block -> _function -> boxed_function
diff --git a/python/Makefile b/python/Makefile
index b461db3..7842eae 100644
--- a/python/Makefile
+++ b/python/Makefile
@@ -3,7 +3,7 @@ TESTS =
SOURCES_BASE = mal_readline.py mal_types.py reader.py printer.py
-SOURCES_LISP = env.py core.py stepA_more.py
+SOURCES_LISP = env.py core.py stepA_mal.py
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
diff --git a/python/core.py b/python/core.py
index d10047d..4a64594 100644
--- a/python/core.py
+++ b/python/core.py
@@ -60,7 +60,9 @@ def cons(x, seq): return List([x]) + List(seq)
def concat(*lsts): return List(chain(*lsts))
-def nth(lst, idx): return lst[idx]
+def nth(lst, idx):
+ if idx < len(lst): return lst[idx]
+ else: throw("nth: index out of range")
def first(lst): return lst[0]
@@ -68,7 +70,9 @@ def rest(lst): return List(lst[1:])
def empty_Q(lst): return len(lst) == 0
-def count(lst): return len(lst)
+def count(lst):
+ if types._nil_Q(lst): return 0
+ else: return len(lst)
# retains metadata
def conj(lst, *args):
@@ -114,6 +118,8 @@ ns = {
'false?': types._false_Q,
'symbol': types._symbol,
'symbol?': types._symbol_Q,
+ 'keyword': types._keyword,
+ 'keyword?': types._keyword_Q,
'pr-str': pr_str,
'str': do_str,
diff --git a/python/mal_types.py b/python/mal_types.py
index e6bd70d..b1b1b3b 100644
--- a/python/mal_types.py
+++ b/python/mal_types.py
@@ -1,4 +1,15 @@
import sys, copy, types as pytypes
+
+# python 3.0 differences
+if sys.hexversion > 0x3000000:
+ def u(x):
+ return x
+else:
+ import codecs
+ def u(x):
+ return codecs.unicode_escape_decode(x)[0]
+
+
if sys.version_info[0] >= 3:
str_types = [str]
else:
@@ -58,6 +69,14 @@ class Symbol(str): pass
def _symbol(str): return Symbol(str)
def _symbol_Q(exp): return type(exp) == Symbol
+# Keywords
+# A specially prefixed string
+def _keyword(str):
+ if str[0] == u("\u029e"): return str
+ else: return u("\u029e") + str
+def _keyword_Q(exp):
+ return _string_Q(exp) and exp[0] == u("\u029e")
+
# Functions
def _function(Eval, Env, ast, env, params):
def fn(*args):
diff --git a/python/printer.py b/python/printer.py
index 65bf256..98e3e90 100644
--- a/python/printer.py
+++ b/python/printer.py
@@ -12,7 +12,9 @@ def _pr_str(obj, print_readably=True):
ret.extend((_pr_str(k), _pr_str(obj[k],_r)))
return "{" + " ".join(ret) + "}"
elif types._string_Q(obj):
- if print_readably:
+ if len(obj) > 0 and obj[0] == types.u('\u029e'):
+ return ':' + obj[1:]
+ elif print_readably:
return '"' + obj.encode('unicode_escape').decode('latin1').replace('"', '\\"') + '"'
else:
return obj
diff --git a/python/reader.py b/python/reader.py
index 13b1f7b..71ad3d6 100644
--- a/python/reader.py
+++ b/python/reader.py
@@ -1,5 +1,5 @@
import re
-from mal_types import (_symbol, _list, _vector, _hash_map)
+from mal_types import (_symbol, _keyword, _list, _vector, _hash_map)
class Blank(Exception): pass
@@ -29,6 +29,7 @@ def read_atom(reader):
if re.match(int_re, token): return int(token)
elif re.match(float_re, token): return int(token)
elif token[0] == '"': return token[1:-1].replace('\\"', '"')
+ elif token[0] == ':': return _keyword(token[1:])
elif token == "nil": return None
elif token == "true": return True
elif token == "false": return False
diff --git a/python/step3_env.py b/python/step3_env.py
index 4565011..86dc176 100644
--- a/python/step3_env.py
+++ b/python/step3_env.py
@@ -58,10 +58,10 @@ repl_env = Env()
def REP(str):
return PRINT(EVAL(READ(str), repl_env))
-repl_env.set('+', lambda a,b: a+b)
-repl_env.set('-', lambda a,b: a-b)
-repl_env.set('*', lambda a,b: a*b)
-repl_env.set('/', lambda a,b: int(a/b))
+repl_env.set(types._symbol('+'), lambda a,b: a+b)
+repl_env.set(types._symbol('-'), lambda a,b: a-b)
+repl_env.set(types._symbol('*'), lambda a,b: a*b)
+repl_env.set(types._symbol('/'), lambda a,b: int(a/b))
# repl loop
while True:
diff --git a/python/step4_if_fn_do.py b/python/step4_if_fn_do.py
index e99cfcf..39b9dd6 100644
--- a/python/step4_if_fn_do.py
+++ b/python/step4_if_fn_do.py
@@ -74,7 +74,7 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
diff --git a/python/step5_tco.py b/python/step5_tco.py
index cbb92c9..da338d4 100644
--- a/python/step5_tco.py
+++ b/python/step5_tco.py
@@ -83,7 +83,7 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
diff --git a/python/step6_file.py b/python/step6_file.py
index 9d84d1f..5d10b46 100644
--- a/python/step6_file.py
+++ b/python/step6_file.py
@@ -83,9 +83,9 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
-repl_env.set('eval', lambda ast: EVAL(ast, repl_env))
-repl_env.set('*ARGV*', types._list(*sys.argv[2:]))
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
+repl_env.set(types._symbol('eval'), lambda ast: EVAL(ast, repl_env))
+repl_env.set(types._symbol('*ARGV*'), types._list(*sys.argv[2:]))
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
diff --git a/python/step7_quote.py b/python/step7_quote.py
index de19c6d..7c97c23 100644
--- a/python/step7_quote.py
+++ b/python/step7_quote.py
@@ -106,9 +106,9 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
-repl_env.set('eval', lambda ast: EVAL(ast, repl_env))
-repl_env.set('*ARGV*', types._list(*sys.argv[2:]))
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
+repl_env.set(types._symbol('eval'), lambda ast: EVAL(ast, repl_env))
+repl_env.set(types._symbol('*ARGV*'), types._list(*sys.argv[2:]))
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
diff --git a/python/step8_macros.py b/python/step8_macros.py
index 80ca239..da863c1 100644
--- a/python/step8_macros.py
+++ b/python/step8_macros.py
@@ -126,9 +126,9 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
-repl_env.set('eval', lambda ast: EVAL(ast, repl_env))
-repl_env.set('*ARGV*', types._list(*sys.argv[2:]))
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
+repl_env.set(types._symbol('eval'), lambda ast: EVAL(ast, repl_env))
+repl_env.set(types._symbol('*ARGV*'), types._list(*sys.argv[2:]))
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
diff --git a/python/step9_interop.py b/python/step9_try.py
index 7cacf1f..e01f42c 100644
--- a/python/step9_interop.py
+++ b/python/step9_try.py
@@ -97,12 +97,17 @@ def EVAL(ast, env):
else:
exec(compile(ast[1], '', 'single') in globals())
return None
- elif "py*" == a0:
- return eval(ast[1])
- elif "." == a0:
- el = eval_ast(ast[2:], env)
- f = eval(ast[1])
- return f(*el)
+ elif "try*" == a0:
+ a1, a2 = ast[1], ast[2]
+ if a2[0] == "catch*":
+ try:
+ return EVAL(a1, env);
+ except Exception as exc:
+ exc = exc.args[0]
+ catch_env = Env(env, [a2[1]], [exc])
+ return EVAL(a2[2], catch_env)
+ else:
+ return EVAL(a1, env);
elif "do" == a0:
eval_ast(ast[1:-1], env)
ast = ast[-1]
@@ -138,9 +143,9 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
-repl_env.set('eval', lambda ast: EVAL(ast, repl_env))
-repl_env.set('*ARGV*', types._list(*sys.argv[2:]))
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
+repl_env.set(types._symbol('eval'), lambda ast: EVAL(ast, repl_env))
+repl_env.set(types._symbol('*ARGV*'), types._list(*sys.argv[2:]))
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
diff --git a/python/stepA_more.py b/python/stepA_mal.py
index 723f0ed..93cdb2e 100644
--- a/python/stepA_more.py
+++ b/python/stepA_mal.py
@@ -149,9 +149,9 @@ def REP(str):
return PRINT(EVAL(READ(str), repl_env))
# core.py: defined using python
-for k, v in core.ns.items(): repl_env.set(k, v)
-repl_env.set('eval', lambda ast: EVAL(ast, repl_env))
-repl_env.set('*ARGV*', types._list(*sys.argv[2:]))
+for k, v in core.ns.items(): repl_env.set(types._symbol(k), v)
+repl_env.set(types._symbol('eval'), lambda ast: EVAL(ast, repl_env))
+repl_env.set(types._symbol('*ARGV*'), types._list(*sys.argv[2:]))
# core.mal: defined using the language itself
REP("(def! *host-language* \"python\")")
diff --git a/r/Makefile b/r/Makefile
new file mode 100644
index 0000000..4d1ec24
--- /dev/null
+++ b/r/Makefile
@@ -0,0 +1,24 @@
+TESTS =
+
+SOURCES_BASE = readline.r types.r reader.r printer.r
+SOURCES_LISP = env.r core.r stepA_mal.r
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+all: libs
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
+
+
+.PHONY:
+libs: lib/rdyncall
+
+lib/rdyncall:
+ curl -O http://cran.r-project.org/src/contrib/Archive/rdyncall/rdyncall_0.7.5.tar.gz
+ mkdir -p lib
+ R CMD INSTALL rdyncall_0.7.5.tar.gz -l lib/
+ rm rdyncall_0.7.5.tar.gz
diff --git a/r/core.r b/r/core.r
new file mode 100644
index 0000000..dab36e3
--- /dev/null
+++ b/r/core.r
@@ -0,0 +1,182 @@
+..core.. <- TRUE
+
+if(!exists("..types..")) source("types.r")
+if(!exists("..printer..")) source("printer.r")
+
+
+# String functions
+
+pr_str <- function(...)
+ .pr_list(list(...), print_readably=TRUE, join=" ")
+
+str <- function(...)
+ .pr_list(list(...), print_readably=FALSE, join="")
+
+prn <- function(...) {
+ cat(.pr_list(list(...), print_readably=TRUE, join=" "))
+ cat("\n")
+ nil
+}
+
+println <- function(...) {
+ cat(.pr_list(list(...), print_readably=FALSE, join=" "))
+ cat("\n")
+ nil
+}
+
+do_readline <- function(prompt) {
+ l <- readline(prompt)
+ if (is.null(l)) nil else l
+}
+
+# Hash Map functions
+do_get <- function(hm,k) {
+ if (class(hm) == "nil") return(nil)
+ v <- hm[[k]]
+ if (is.null(v)) nil else v
+}
+contains_q <-function(hm,k) {
+ if (class(hm) == "nil") return(FALSE)
+ if (is.null(hm[[k]])) FALSE else TRUE
+}
+
+# Sequence functions
+cons <- function(a,b) {
+ new_lst <- append(list(a), b)
+ new.listl(new_lst)
+}
+
+nth <- function(a,b) {
+ if (b < length(a))
+ a[[b+1]]
+ else
+ throw("nth: index out of range")
+}
+
+do_concat <- function(...) {
+ new_lst <- list()
+ for(l in list(...)) {
+ new_lst <- append(new_lst, l)
+ }
+ new.listl(new_lst)
+}
+
+do_apply <- function(f, ...) {
+ p <- list(...)
+ args <- list()
+ if (length(p) > 1) {
+ for(l in slice(p, 1, length(p)-1)) {
+ args[[length(args)+1]] <- l
+ }
+ }
+ args <- append(args, p[[length(p)]])
+ fapply(f, args)
+}
+
+map <- function(f, seq) {
+ new.listl(lapply(seq, function(el) fapply(f, list(el))))
+}
+
+conj <- function(obj, ...) {
+ p <- list(...)
+ new_obj <- .clone(obj)
+ if (.list_q(obj)) {
+ if (length(p) > 0) {
+ for(l in p) new_obj <- append(list(l), new_obj)
+ }
+ new.listl(new_obj)
+ } else if (.vector_q(obj)) {
+ if (length(p) > 0) {
+ for(l in p) new_obj <- append(new_obj, list(l))
+ }
+ new.vectorl(new_obj)
+ } else {
+ throw("conj called on non-sequence")
+ }
+}
+
+# Metadata functions
+with_meta <- function(obj, m) {
+ new_obj <- .clone(obj)
+ attr(new_obj, "meta") <- m
+ new_obj
+}
+
+meta <- function(obj) {
+ m <- attr(obj, "meta")
+ if (is.null(m)) nil else m
+}
+
+# Atom functions
+deref <- function(atm) atm$val
+reset_bang <- function (atm, val) { atm$val <- val; val }
+swap_bang <- function (atm, f, ...) {
+ p <- list(...)
+ args <- list(atm$val)
+ if (length(p) > 0) {
+ for(l in p) args[[length(args)+1]] <- l
+ }
+ atm$val <- fapply(f, args)
+}
+
+core_ns <- list(
+ "="=function(a,b) .equal_q(a,b),
+ "throw"=function(err) throw(err),
+ "nil?"=.nil_q,
+ "true?"=.true_q,
+ "false?"=.false_q,
+ "symbol"=new.symbol,
+ "symbol?"=.symbol_q,
+ "keyword"=new.keyword,
+ "keyword?"=.keyword_q,
+
+ "pr-str"=pr_str,
+ "str"=str,
+ "prn"=prn,
+ "println"=println,
+ "readline"=do_readline,
+ "read-string"=function(str) read_str(str),
+ "slurp"=function(path) readChar(path, file.info(path)$size),
+ "<"=function(a,b) a<b,
+ "<="=function(a,b) a<=b,
+ ">"=function(a,b) a>b,
+ ">="=function(a,b) a>=b,
+ "+"=function(a,b) a+b,
+ "-"=function(a,b) a-b,
+ "*"=function(a,b) a*b,
+ "/"=function(a,b) a/b,
+ "time-ms"=function() round(as.numeric(Sys.time())*1000),
+
+ "list"=new.list,
+ "list?"=function(a) .list_q(a),
+ "vector"=new.vector,
+ "vector?"=function(a) .vector_q(a),
+ "hash-map"=new.hash_map,
+ "map?"=function(a) .hash_map_q(a),
+ "assoc"=function(hm,...) .assoc(hm,list(...)),
+ "dissoc"=function(hm,...) .dissoc(hm,list(...)),
+ "get"=do_get,
+ "contains?"=contains_q,
+ "keys"=function(hm) new.listl(ls(hm)),
+ "vals"=function(hm) new.listl(lapply(ls(hm), function(x) hm[[x]])),
+
+ "sequential?"=.sequential_q,
+ "cons"=cons,
+ "concat"=do_concat,
+ "nth"=nth,
+ "first"=function(a) if (length(a) < 1) nil else a[[1]],
+ "rest"=function(a) new.listl(slice(a,2)),
+ "empty?"=function(a) .sequential_q(a) && length(a) == 0,
+ "count"=function(a) if (.nil_q(a)) 0 else length(a),
+ "apply"=do_apply,
+ "map"=map,
+ "conj"=conj,
+
+ "with-meta"=with_meta,
+ "meta"=meta,
+ "atom"=new.atom,
+ "atom?"=.atom_q,
+ "deref"=deref,
+ "reset!"=reset_bang,
+ "swap!"=swap_bang
+)
diff --git a/r/env.r b/r/env.r
new file mode 100644
index 0000000..6924881
--- /dev/null
+++ b/r/env.r
@@ -0,0 +1,42 @@
+..env.. <- TRUE
+
+if(!exists("..types..")) source("types.r")
+
+new.Env <- function(outer=emptyenv(), binds=list(), exprs=list()) {
+ e <- structure(new.env(parent=outer), class="Env")
+
+ if (length(binds) > 0) {
+ for(i in seq(length(binds))) {
+ b <- binds[[i]]
+ if (b == "&") {
+ e[[binds[[i+1]]]] <-
+ slice(exprs, i, length(exprs))
+ break
+ } else {
+ e[[b]] <- exprs[[i]]
+ }
+ }
+ }
+ e
+}
+
+Env.find <- function(e, key) {
+ if (exists(key, envir=e, inherits=FALSE)) {
+ e
+ } else if (!identical(parent.env(e), emptyenv())) {
+ Env.find(parent.env(e), key)
+ } else {
+ nil
+ }
+}
+
+Env.set <- function(e, key, val) {
+ e[[key]] <- val
+ invisible(val)
+}
+
+Env.get <- function(e, key) {
+ e <- Env.find(e, key)
+ if (.nil_q(e)) throw(concat("'", key, "' not found"))
+ e[[key]]
+}
diff --git a/r/printer.r b/r/printer.r
new file mode 100644
index 0000000..71deef7
--- /dev/null
+++ b/r/printer.r
@@ -0,0 +1,54 @@
+..printer.. <- TRUE
+
+if(!exists("..types..")) source("types.r")
+
+.pr_list <- function(lst, print_readably=TRUE, join="") {
+ concatl(lapply(lst,
+ function(e) .pr_str(e, print_readably)), sep=join)
+}
+
+.pr_str <- function(exp, print_readably=TRUE) {
+ pr <- print_readably
+ switch(class(exp),
+ "List"={
+ paste("(", .pr_list(exp, pr, " "), ")", sep="", collapse="")
+ },
+ "Vector"={
+ paste("[", .pr_list(exp, pr, " "), "]", sep="", collapse="")
+ },
+ "HashMap"={
+ hlst <- list()
+ if (length(exp) > 0) {
+ for(k in ls(exp)) {
+ hlst[[length(hlst)+1]] <- k
+ hlst[[length(hlst)+1]] <- exp[[k]]
+ }
+ }
+ paste("{", .pr_list(hlst, pr, " "), "}", sep="", collapse="")
+ },
+ "character"={
+ if (substring(exp,1,1) == "\u029e") {
+ concat(":", substring(exp,2))
+ } else if (print_readably) {
+ paste("\"",
+ gsub("\\n", "\\\\n",
+ gsub("\\\"", "\\\\\"",
+ gsub("\\\\", "\\\\\\\\", exp))),
+ "\"", sep="", collapse="")
+ } else {
+ exp
+ }
+ },
+ "Symbol"={ exp },
+ "nil"={ "nil" },
+ "logical"={ tolower(exp) },
+ "MalFunc"={
+ paste("(fn* ", .pr_str(exp$params,TRUE),
+ " ", .pr_str(exp$ast, TRUE), ")", sep="")
+ },
+ "function"={ "<#function>" },
+ "Atom"={
+ paste("(atom ", .pr_str(exp$val,TRUE), ")", sep="")
+ },
+ { toString(exp) })
+}
diff --git a/r/reader.r b/r/reader.r
new file mode 100644
index 0000000..7f20288
--- /dev/null
+++ b/r/reader.r
@@ -0,0 +1,137 @@
+..reader.. <- TRUE
+
+if(!exists("..types..")) source("types.r")
+
+new.Reader <- function(tokens) {
+ e <- structure(new.env(), class="Reader")
+ e$tokens <- tokens
+ e$position <- 1
+ e
+}
+
+Reader.peek <- function(rdr) {
+ if (rdr$position > length(rdr$tokens)) return(NULL)
+ rdr$tokens[[rdr$position]]
+}
+
+Reader.next <- function(rdr) {
+ if (rdr$position > length(rdr$tokens)) return(NULL)
+ rdr$position <- rdr$position + 1
+ rdr$tokens[[rdr$position-1]]
+}
+
+tokenize <- function(str) {
+ re <- "[\\s,]*(~@|[\\[\\]\\{\\}\\(\\)'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]\\{\\}\\('\"`,;\\)]*)"
+ m <- lapply(regmatches(str, gregexpr(re, str, perl=TRUE)),
+ function(e) sub("^[\\s,]+", "", e, perl=TRUE))
+ res <- list()
+ i <- 1
+ for(v in m[[1]]) {
+ if (v == "" || substr(v,1,1) == ";") next
+ res[[i]] <- v
+ i <- i+1
+ }
+ res
+}
+
+re_match <- function(re, str) { length(grep(re, c(str))) > 0 }
+
+read_atom <- function(rdr) {
+ token <- Reader.next(rdr)
+ if (re_match("^-?[0-9]+$", token)) {
+ as.integer(token)
+ } else if (re_match("^-?[0-9][0-9.]*$", token)) {
+ as.double(token)
+ } else if (substr(token,1,1) == "\"") {
+ gsub("\\\\n", "\\n",
+ gsub("\\\\\"", "\"",
+ substr(token, 2, nchar(token)-1)))
+ } else if (substr(token,1,1) == ":") {
+ new.keyword(substring(token,2))
+ } else if (token == "nil") {
+ nil
+ } else if (token == "true") {
+ TRUE
+ } else if (token == "false") {
+ FALSE
+ } else {
+ new.symbol(token)
+ }
+}
+
+read_seq <- function(rdr, start="(", end=")") {
+ lst <- list()
+ token <- Reader.next(rdr)
+ if (token != start) {
+ throw(concat("expected '", start, "'"))
+ }
+ repeat {
+ token <- Reader.peek(rdr)
+ if (is.null(token)) {
+ throw(concat("expected '", end, "', got EOF"))
+ }
+ if (token == end) break
+ lst[[length(lst)+1]] <- read_form(rdr)
+ }
+ Reader.next(rdr)
+ new.listl(lst)
+}
+
+read_form <- function(rdr) {
+ token <- Reader.peek(rdr)
+ if (token == "'") {
+ . <- Reader.next(rdr);
+ new.list(new.symbol("quote"), read_form(rdr))
+ } else if (token == "`") {
+ . <- Reader.next(rdr);
+ new.list(new.symbol("quasiquote"), read_form(rdr))
+ } else if (token == "~") {
+ . <- Reader.next(rdr);
+ new.list(new.symbol("unquote"), read_form(rdr))
+ } else if (token == "~@") {
+ . <- Reader.next(rdr);
+ new.list(new.symbol("splice-unquote"), read_form(rdr))
+ } else if (token == "^") {
+ . <- Reader.next(rdr)
+ m <- read_form(rdr)
+ new.list(new.symbol("with-meta"), read_form(rdr), m)
+ } else if (token == "@") {
+ . <- Reader.next(rdr);
+ new.list(new.symbol("deref"), read_form(rdr))
+ } else if (token == ")") {
+ throw("unexpected ')'")
+ } else if (token == "(") {
+ new.listl(read_seq(rdr))
+ } else if (token == "]") {
+ throw("unexpected ']'")
+ } else if (token == "[") {
+ new.vectorl(read_seq(rdr, "[", "]"))
+ } else if (token == "}") {
+ throw("unexpected '}'")
+ } else if (token == "{") {
+ new.hash_mapl(read_seq(rdr, "{", "}"))
+ } else {
+ read_atom(rdr)
+ }
+}
+
+read_str <- function(str) {
+ tokens <- tokenize(str)
+ if (length(tokens) == 0) return(nil)
+ return(read_form(new.Reader(tokens)))
+}
+
+#cat("---\n")
+#print(tokenize("123"))
+#cat("---\n")
+#print(tokenize(" ( 123 456 abc \"def\" ) "))
+
+#rdr <- new.reader(tokenize(" ( 123 456 abc \"def\" ) "))
+#Reader.peek(rdr)
+#Reader.next(rdr)
+#Reader.next(rdr)
+#Reader.next(rdr)
+#Reader.next(rdr)
+#Reader.next(rdr)
+#Reader.next(rdr)
+#Reader.next(rdr)
diff --git a/r/readline.r b/r/readline.r
new file mode 100644
index 0000000..79ababc
--- /dev/null
+++ b/r/readline.r
@@ -0,0 +1,40 @@
+..readline.. <- TRUE
+
+HISTORY_FILE = paste(path.expand("~"), "/.mal-history", sep="")
+
+library(rdyncall, lib.loc="lib/")
+
+#.rllib <- dynfind(c("edit"))
+.rllib <- dynfind(c("readline"))
+.call_readline <- .dynsym(.rllib,"readline")
+.call_add_history <- .dynsym(.rllib,"add_history")
+
+.state <- new.env()
+.state$rl_history_loaded = FALSE
+
+.readline <- function(prompt) {
+ res <- .dyncall(.call_readline, "Z)p", prompt)
+ if (is.nullptr(res)) {
+ return(NULL)
+ } else {
+ return(ptr2str(res))
+ }
+}
+
+readline <- function(prompt) {
+ if (!.state$rl_history_loaded) {
+ .state$rl_history_loaded <- TRUE
+
+ lines <- scan(HISTORY_FILE, what="", sep="\n", quiet=TRUE)
+ for(add_line in lines) {
+ .dyncall(.call_add_history, "Z)v", add_line)
+ }
+ }
+
+ line <- .readline(prompt)
+ if (is.null(line)) return(NULL)
+ .dyncall(.call_add_history, "Z)v", line)
+ write(line, file=HISTORY_FILE, append=TRUE)
+
+ line
+}
diff --git a/r/step0_repl.r b/r/step0_repl.r
new file mode 100644
index 0000000..7b03dd3
--- /dev/null
+++ b/r/step0_repl.r
@@ -0,0 +1,27 @@
+source("readline.r")
+
+READ <- function(str) {
+ return(str)
+}
+
+EVAL <- function(ast, env) {
+ return(ast)
+}
+
+PRINT <- function(exp) {
+ return(exp)
+}
+
+rep <- function(str) {
+ return(PRINT(EVAL(READ(str), "")))
+}
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", err$message,"\n", sep="")
+ })
+}
diff --git a/r/step1_read_print.r b/r/step1_read_print.r
new file mode 100644
index 0000000..39d189b
--- /dev/null
+++ b/r/step1_read_print.r
@@ -0,0 +1,32 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+
+READ <- function(str) {
+ return(read_str(str))
+}
+
+EVAL <- function(ast, env) {
+ return(ast)
+}
+
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+rep <- function(str) {
+ return(PRINT(EVAL(READ(str), "")))
+}
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step2_eval.r b/r/step2_eval.r
new file mode 100644
index 0000000..45036f0
--- /dev/null
+++ b/r/step2_eval.r
@@ -0,0 +1,63 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+
+READ <- function(str) {
+ return(read_str(str))
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ env[[as.character(ast)]]
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ return(do.call(f,el[-1]))
+}
+
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+repl_env <- new.env()
+repl_env[["+"]] <- function(a,b) a+b
+repl_env[["-"]] <- function(a,b) a-b
+repl_env[["*"]] <- function(a,b) a*b
+repl_env[["/"]] <- function(a,b) a/b
+
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step3_env.r b/r/step3_env.r
new file mode 100644
index 0000000..142f43d
--- /dev/null
+++ b/r/step3_env.r
@@ -0,0 +1,81 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+
+READ <- function(str) {
+ return(read_str(str))
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(ast[[3]], env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ return(EVAL(a2, let_env))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ return(do.call(f,slice(el,2)))
+ }
+}
+
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+repl_env <- new.Env()
+Env.set(repl_env, "+", function(a,b) a+b)
+Env.set(repl_env, "-", function(a,b) a-b)
+Env.set(repl_env, "*", function(a,b) a*b)
+Env.set(repl_env, "/", function(a,b) a/b)
+
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step4_if_fn_do.r b/r/step4_if_fn_do.r
new file mode 100644
index 0000000..567e18d
--- /dev/null
+++ b/r/step4_if_fn_do.r
@@ -0,0 +1,100 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+READ <- function(str) {
+ return(read_str(str))
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(ast[[3]], env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ return(EVAL(a2, let_env))
+ } else if (a0sym == "do") {
+ el <- eval_ast(slice(ast,2), env)
+ return(el[[length(el)]])
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ return(EVAL(ast[[4]], env))
+ } else {
+ return(EVAL(a2, env))
+ }
+ } else if (a0sym == "fn*") {
+ return(function(...) {
+ EVAL(a2, new.Env(env, a1, list(...)))
+ })
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ return(do.call(f,slice(el,2)))
+ }
+}
+
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+
+# core.mal: defined using the language itself
+. <- rep("(def! not (fn* (a) (if a false true)))")
+
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step5_tco.r b/r/step5_tco.r
new file mode 100644
index 0000000..913c78f
--- /dev/null
+++ b/r/step5_tco.r
@@ -0,0 +1,108 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+READ <- function(str) {
+ return(read_str(str))
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ repeat {
+
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(a2, env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ ast <- a2
+ env <- let_env
+ } else if (a0sym == "do") {
+ eval_ast(slice(ast,2,length(ast)-1), env)
+ ast <- ast[[length(ast)]]
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ ast <- ast[[4]]
+ } else {
+ ast <- a2
+ }
+ } else if (a0sym == "fn*") {
+ return(malfunc(EVAL, a2, env, a1))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ if (class(f) == "MalFunc") {
+ ast <- f$ast
+ env <- f$gen_env(slice(el,2))
+ } else {
+ return(do.call(f,slice(el,2)))
+ }
+ }
+
+ }
+}
+
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+
+# core.mal: defined using the language itself
+. <- rep("(def! not (fn* (a) (if a false true)))")
+
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step6_file.r b/r/step6_file.r
new file mode 100644
index 0000000..a1e5947
--- /dev/null
+++ b/r/step6_file.r
@@ -0,0 +1,120 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+# read
+READ <- function(str) {
+ return(read_str(str))
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ repeat {
+
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(a2, env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ ast <- a2
+ env <- let_env
+ } else if (a0sym == "do") {
+ eval_ast(slice(ast,2,length(ast)-1), env)
+ ast <- ast[[length(ast)]]
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ ast <- ast[[4]]
+ } else {
+ ast <- a2
+ }
+ } else if (a0sym == "fn*") {
+ return(malfunc(EVAL, a2, env, a1))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ if (class(f) == "MalFunc") {
+ ast <- f$ast
+ env <- f$gen_env(slice(el,2))
+ } else {
+ return(do.call(f,slice(el,2)))
+ }
+ }
+
+ }
+}
+
+# print
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+# repl loop
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+Env.set(repl_env, "eval", function(ast) EVAL(ast, repl_env))
+Env.set(repl_env, "*ARGV*", new.list())
+
+# 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) \")\")))))")
+
+args <- commandArgs(trailingOnly = TRUE)
+if (length(args) > 0) {
+ Env.set(repl_env, "*ARGV*", new.listl(slice(list(args),2)))
+ . <- rep(concat("(load-file \"", args[[1]], "\")"))
+ quit(save="no", status=0)
+}
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step7_quote.r b/r/step7_quote.r
new file mode 100644
index 0000000..9abfdde
--- /dev/null
+++ b/r/step7_quote.r
@@ -0,0 +1,148 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+# read
+READ <- function(str) {
+ return(read_str(str))
+}
+
+# eval
+is_pair <- function(x) {
+ .sequential_q(x) && length(x) > 0
+}
+
+quasiquote <- function(ast) {
+ if (!is_pair(ast)) {
+ new.list(new.symbol("quote"),
+ ast)
+ } else if (.symbol_q(ast[[1]]) && ast[[1]] == "unquote") {
+ ast[[2]]
+ } else if (is_pair(ast[[1]]) &&
+ .symbol_q(ast[[1]][[1]]) &&
+ ast[[1]][[1]] == "splice-unquote") {
+ new.list(new.symbol("concat"),
+ ast[[1]][[2]],
+ quasiquote(slice(ast, 2)))
+ } else {
+ new.list(new.symbol("cons"),
+ quasiquote(ast[[1]]),
+ quasiquote(slice(ast, 2)))
+ }
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ repeat {
+
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(a2, env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ ast <- a2
+ env <- let_env
+ } else if (a0sym == "quote") {
+ return(a1)
+ } else if (a0sym == "quasiquote") {
+ ast <- quasiquote(a1)
+ } else if (a0sym == "do") {
+ eval_ast(slice(ast,2,length(ast)-1), env)
+ ast <- ast[[length(ast)]]
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ ast <- ast[[4]]
+ } else {
+ ast <- a2
+ }
+ } else if (a0sym == "fn*") {
+ return(malfunc(EVAL, a2, env, a1))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ if (class(f) == "MalFunc") {
+ ast <- f$ast
+ env <- f$gen_env(slice(el,2))
+ } else {
+ return(do.call(f,slice(el,2)))
+ }
+ }
+
+ }
+}
+
+# print
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+# repl loop
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+Env.set(repl_env, "eval", function(ast) EVAL(ast, repl_env))
+Env.set(repl_env, "*ARGV*", new.list())
+
+# 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) \")\")))))")
+
+args <- commandArgs(trailingOnly = TRUE)
+if (length(args) > 0) {
+ Env.set(repl_env, "*ARGV*", new.listl(slice(list(args),2)))
+ . <- rep(concat("(load-file \"", args[[1]], "\")"))
+ quit(save="no", status=0)
+}
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step8_macros.r b/r/step8_macros.r
new file mode 100644
index 0000000..00931d8
--- /dev/null
+++ b/r/step8_macros.r
@@ -0,0 +1,178 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+# read
+READ <- function(str) {
+ return(read_str(str))
+}
+
+# eval
+is_pair <- function(x) {
+ .sequential_q(x) && length(x) > 0
+}
+
+quasiquote <- function(ast) {
+ if (!is_pair(ast)) {
+ new.list(new.symbol("quote"),
+ ast)
+ } else if (.symbol_q(ast[[1]]) && ast[[1]] == "unquote") {
+ ast[[2]]
+ } else if (is_pair(ast[[1]]) &&
+ .symbol_q(ast[[1]][[1]]) &&
+ ast[[1]][[1]] == "splice-unquote") {
+ new.list(new.symbol("concat"),
+ ast[[1]][[2]],
+ quasiquote(slice(ast, 2)))
+ } else {
+ new.list(new.symbol("cons"),
+ quasiquote(ast[[1]]),
+ quasiquote(slice(ast, 2)))
+ }
+}
+
+is_macro_call <- function(ast, env) {
+ if(.list_q(ast) &&
+ .symbol_q(ast[[1]]) &&
+ (!.nil_q(Env.find(env, ast[[1]])))) {
+ exp <- Env.get(env, ast[[1]])
+ return(.malfunc_q(exp) && exp$ismacro)
+ }
+ FALSE
+}
+
+macroexpand <- function(ast, env) {
+ while(is_macro_call(ast, env)) {
+ mac <- Env.get(env, ast[[1]])
+ ast <- fapply(mac, slice(ast, 2))
+ }
+ ast
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ repeat {
+
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ ast <- macroexpand(ast, env)
+ if (!.list_q(ast)) return(ast)
+
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(a2, env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ ast <- a2
+ env <- let_env
+ } else if (a0sym == "quote") {
+ return(a1)
+ } else if (a0sym == "quasiquote") {
+ ast <- quasiquote(a1)
+ } else if (a0sym == "defmacro!") {
+ func <- EVAL(a2, env)
+ func$ismacro = TRUE
+ return(Env.set(env, a1, func))
+ } else if (a0sym == "macroexpand") {
+ return(macroexpand(a1, env))
+ } else if (a0sym == "do") {
+ eval_ast(slice(ast,2,length(ast)-1), env)
+ ast <- ast[[length(ast)]]
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ ast <- ast[[4]]
+ } else {
+ ast <- a2
+ }
+ } else if (a0sym == "fn*") {
+ return(malfunc(EVAL, a2, env, a1))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ if (class(f) == "MalFunc") {
+ ast <- f$ast
+ env <- f$gen_env(slice(el,2))
+ } else {
+ return(do.call(f,slice(el,2)))
+ }
+ }
+
+ }
+}
+
+# print
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+# repl loop
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+Env.set(repl_env, "eval", function(ast) EVAL(ast, repl_env))
+Env.set(repl_env, "*ARGV*", new.list())
+
+# 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))))))))")
+
+
+args <- commandArgs(trailingOnly = TRUE)
+if (length(args) > 0) {
+ Env.set(repl_env, "*ARGV*", new.listl(slice(list(args),2)))
+ . <- rep(concat("(load-file \"", args[[1]], "\")"))
+ quit(save="no", status=0)
+}
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/step9_try.r b/r/step9_try.r
new file mode 100644
index 0000000..02a6507
--- /dev/null
+++ b/r/step9_try.r
@@ -0,0 +1,196 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+# read
+READ <- function(str) {
+ return(read_str(str))
+}
+
+# eval
+is_pair <- function(x) {
+ .sequential_q(x) && length(x) > 0
+}
+
+quasiquote <- function(ast) {
+ if (!is_pair(ast)) {
+ new.list(new.symbol("quote"),
+ ast)
+ } else if (.symbol_q(ast[[1]]) && ast[[1]] == "unquote") {
+ ast[[2]]
+ } else if (is_pair(ast[[1]]) &&
+ .symbol_q(ast[[1]][[1]]) &&
+ ast[[1]][[1]] == "splice-unquote") {
+ new.list(new.symbol("concat"),
+ ast[[1]][[2]],
+ quasiquote(slice(ast, 2)))
+ } else {
+ new.list(new.symbol("cons"),
+ quasiquote(ast[[1]]),
+ quasiquote(slice(ast, 2)))
+ }
+}
+
+is_macro_call <- function(ast, env) {
+ if(.list_q(ast) &&
+ .symbol_q(ast[[1]]) &&
+ (!.nil_q(Env.find(env, ast[[1]])))) {
+ exp <- Env.get(env, ast[[1]])
+ return(.malfunc_q(exp) && exp$ismacro)
+ }
+ FALSE
+}
+
+macroexpand <- function(ast, env) {
+ while(is_macro_call(ast, env)) {
+ mac <- Env.get(env, ast[[1]])
+ ast <- fapply(mac, slice(ast, 2))
+ }
+ ast
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ repeat {
+
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ ast <- macroexpand(ast, env)
+ if (!.list_q(ast)) return(ast)
+
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(a2, env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ ast <- a2
+ env <- let_env
+ } else if (a0sym == "quote") {
+ return(a1)
+ } else if (a0sym == "quasiquote") {
+ ast <- quasiquote(a1)
+ } else if (a0sym == "defmacro!") {
+ func <- EVAL(a2, env)
+ func$ismacro = TRUE
+ return(Env.set(env, a1, func))
+ } else if (a0sym == "macroexpand") {
+ return(macroexpand(a1, env))
+ } else if (a0sym == "try*") {
+ edata <- new.env()
+ tryCatch({
+ return(EVAL(a1, env))
+ }, error=function(err) {
+ edata$exc <- get_error(err)
+ })
+ if ((!is.null(a2)) && a2[[1]] == "catch*") {
+ return(EVAL(a2[[3]], new.Env(env,
+ new.list(a2[[2]]),
+ new.list(edata$exc))))
+ } else {
+ throw(err)
+ }
+ } else if (a0sym == "do") {
+ eval_ast(slice(ast,2,length(ast)-1), env)
+ ast <- ast[[length(ast)]]
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ ast <- ast[[4]]
+ } else {
+ ast <- a2
+ }
+ } else if (a0sym == "fn*") {
+ return(malfunc(EVAL, a2, env, a1))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ if (class(f) == "MalFunc") {
+ ast <- f$ast
+ env <- f$gen_env(slice(el,2))
+ } else {
+ return(do.call(f,slice(el,2)))
+ }
+ }
+
+ }
+}
+
+# print
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+# repl loop
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+Env.set(repl_env, "eval", function(ast) EVAL(ast, repl_env))
+Env.set(repl_env, "*ARGV*", new.list())
+
+# 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))))))))")
+
+
+args <- commandArgs(trailingOnly = TRUE)
+if (length(args) > 0) {
+ Env.set(repl_env, "*ARGV*", new.listl(slice(list(args),2)))
+ tryCatch({
+ . <- rep(concat("(load-file \"", args[[1]], "\")"))
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ quit(save="no", status=0)
+}
+
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/stepA_mal.r b/r/stepA_mal.r
new file mode 100644
index 0000000..e699048
--- /dev/null
+++ b/r/stepA_mal.r
@@ -0,0 +1,198 @@
+if(!exists("..readline..")) source("readline.r")
+if(!exists("..types..")) source("types.r")
+if(!exists("..reader..")) source("reader.r")
+if(!exists("..printer..")) source("printer.r")
+if(!exists("..env..")) source("env.r")
+if(!exists("..core..")) source("core.r")
+
+# read
+READ <- function(str) {
+ return(read_str(str))
+}
+
+# eval
+is_pair <- function(x) {
+ .sequential_q(x) && length(x) > 0
+}
+
+quasiquote <- function(ast) {
+ if (!is_pair(ast)) {
+ new.list(new.symbol("quote"),
+ ast)
+ } else if (.symbol_q(ast[[1]]) && ast[[1]] == "unquote") {
+ ast[[2]]
+ } else if (is_pair(ast[[1]]) &&
+ .symbol_q(ast[[1]][[1]]) &&
+ ast[[1]][[1]] == "splice-unquote") {
+ new.list(new.symbol("concat"),
+ ast[[1]][[2]],
+ quasiquote(slice(ast, 2)))
+ } else {
+ new.list(new.symbol("cons"),
+ quasiquote(ast[[1]]),
+ quasiquote(slice(ast, 2)))
+ }
+}
+
+is_macro_call <- function(ast, env) {
+ if(.list_q(ast) &&
+ .symbol_q(ast[[1]]) &&
+ (!.nil_q(Env.find(env, ast[[1]])))) {
+ exp <- Env.get(env, ast[[1]])
+ return(.malfunc_q(exp) && exp$ismacro)
+ }
+ FALSE
+}
+
+macroexpand <- function(ast, env) {
+ while(is_macro_call(ast, env)) {
+ mac <- Env.get(env, ast[[1]])
+ ast <- fapply(mac, slice(ast, 2))
+ }
+ ast
+}
+
+eval_ast <- function(ast, env) {
+ if (.symbol_q(ast)) {
+ Env.get(env, ast)
+ } else if (.list_q(ast)) {
+ new.listl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.vector_q(ast)) {
+ new.vectorl(lapply(ast, function(a) EVAL(a, env)))
+ } else if (.hash_map_q(ast)) {
+ lst <- list()
+ for(k in ls(ast)) {
+ lst[[length(lst)+1]] = k
+ lst[[length(lst)+1]] = EVAL(ast[[k]], env)
+ }
+ new.hash_mapl(lst)
+ } else {
+ ast
+ }
+}
+
+EVAL <- function(ast, env) {
+ repeat {
+
+ #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="")
+ if (!.list_q(ast)) {
+ return(eval_ast(ast, env))
+ }
+
+ # apply list
+ ast <- macroexpand(ast, env)
+ if (!.list_q(ast)) return(ast)
+
+ switch(paste("l",length(ast),sep=""),
+ l0={ return(ast) },
+ l1={ a0 <- ast[[1]]; a1 <- NULL; a2 <- NULL },
+ l2={ a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- NULL },
+ { a0 <- ast[[1]]; a1 <- ast[[2]]; a2 <- ast[[3]] })
+ if (length(a0) > 1) a0sym <- "__<*fn*>__"
+ else a0sym <- as.character(a0)
+ if (a0sym == "def!") {
+ res <- EVAL(a2, env)
+ return(Env.set(env, a1, res))
+ } else if (a0sym == "let*") {
+ let_env <- new.Env(env)
+ for(i in seq(1,length(a1),2)) {
+ Env.set(let_env, a1[[i]], EVAL(a1[[i+1]], let_env))
+ }
+ ast <- a2
+ env <- let_env
+ } else if (a0sym == "quote") {
+ return(a1)
+ } else if (a0sym == "quasiquote") {
+ ast <- quasiquote(a1)
+ } else if (a0sym == "defmacro!") {
+ func <- EVAL(a2, env)
+ func$ismacro = TRUE
+ return(Env.set(env, a1, func))
+ } else if (a0sym == "macroexpand") {
+ return(macroexpand(a1, env))
+ } else if (a0sym == "try*") {
+ edata <- new.env()
+ tryCatch({
+ return(EVAL(a1, env))
+ }, error=function(err) {
+ edata$exc <- get_error(err)
+ })
+ if ((!is.null(a2)) && a2[[1]] == "catch*") {
+ return(EVAL(a2[[3]], new.Env(env,
+ new.list(a2[[2]]),
+ new.list(edata$exc))))
+ } else {
+ throw(err)
+ }
+ } else if (a0sym == "do") {
+ eval_ast(slice(ast,2,length(ast)-1), env)
+ ast <- ast[[length(ast)]]
+ } else if (a0sym == "if") {
+ cond <- EVAL(a1, env)
+ if (.nil_q(cond) || identical(cond, FALSE)) {
+ if (length(ast) < 4) return(nil)
+ ast <- ast[[4]]
+ } else {
+ ast <- a2
+ }
+ } else if (a0sym == "fn*") {
+ return(malfunc(EVAL, a2, env, a1))
+ } else {
+ el <- eval_ast(ast, env)
+ f <- el[[1]]
+ if (class(f) == "MalFunc") {
+ ast <- f$ast
+ env <- f$gen_env(slice(el,2))
+ } else {
+ return(do.call(f,slice(el,2)))
+ }
+ }
+
+ }
+}
+
+# print
+PRINT <- function(exp) {
+ return(.pr_str(exp, TRUE))
+}
+
+# repl loop
+repl_env <- new.Env()
+rep <- function(str) return(PRINT(EVAL(READ(str), repl_env)))
+
+# core.r: defined using R
+for(k in names(core_ns)) { Env.set(repl_env, k, core_ns[[k]]) }
+Env.set(repl_env, "eval", function(ast) EVAL(ast, repl_env))
+Env.set(repl_env, "*ARGV*", new.list())
+
+# core.mal: defined using the language itself
+. <- rep("(def! *host-language* \"R\")")
+. <- 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))))))))")
+
+
+args <- commandArgs(trailingOnly = TRUE)
+if (length(args) > 0) {
+ Env.set(repl_env, "*ARGV*", new.listl(slice(list(args),2)))
+ tryCatch({
+ . <- rep(concat("(load-file \"", args[[1]], "\")"))
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ quit(save="no", status=0)
+}
+
+. <- rep("(println (str \"Mal [\" *host-language* \"]\"))")
+repeat {
+ line <- readline("user> ")
+ if (is.null(line)) { cat("\n"); break }
+ tryCatch({
+ cat(rep(line),"\n", sep="")
+ }, error=function(err) {
+ cat("Error: ", get_error(err),"\n", sep="")
+ })
+ # R debug/fatal with tracebacks:
+ #cat(rep(line),"\n", sep="")
+}
diff --git a/r/types.r b/r/types.r
new file mode 100644
index 0000000..92d1357
--- /dev/null
+++ b/r/types.r
@@ -0,0 +1,159 @@
+..types.. <- TRUE
+
+if(!exists("..env..")) source("env.r")
+
+# General type related functions
+concat <- function(..., sep="") paste(..., collapse="", sep=sep)
+concatl <- function(lst, sep="") paste(lst, collapse=sep, sep=sep)
+
+slice <- function(seq, start=1, end=-1) {
+ if (end == -1) end <- length(seq)
+ if (start > end) lst <- list() else lst <- seq[start:end]
+ switch(class(seq),
+ list={ new.listl(lst) },
+ List={ new.listl(lst) },
+ Vector={ new.vectorl(lst) },
+ { throw("slice called on non-sequence") })
+}
+
+.sequential_q <- function(obj) .list_q(obj) || .vector_q(obj)
+
+.equal_q <- function(a,b) {
+ ota <- class(a); otb <- class(b)
+ if (!((ota == otb) || (.sequential_q(a) && .sequential_q(b)))) {
+ return(FALSE)
+ }
+ switch(ota,
+ "List"={
+ if (length(a) != length(b)) return(FALSE)
+ if (length(a) == 0) return(TRUE)
+ for(i in seq(length(a))) {
+ if (!.equal_q(a[[i]],b[[i]])) return(FALSE)
+ }
+ TRUE
+ },
+ "Vector"={
+ if (length(a) != length(b)) return(FALSE)
+ if (length(a) == 0) return(TRUE)
+ for(i in seq(length(a))) {
+ if (!.equal_q(a[[i]],b[[i]])) return(FALSE)
+ }
+ TRUE
+ },
+ {
+ a == b
+ })
+}
+
+.clone <- function(obj) {
+ if (.hash_map_q(obj)) {
+ new_obj <- new.env()
+ for(k in ls(obj, all.names=TRUE)) new_obj[[k]] = obj[[k]]
+ class(new_obj) <- "HashMap"
+ } else {
+ new_obj <- obj
+ }
+ new_obj
+}
+
+# Errors/exceptions
+thrown_error = new.env()
+thrown_error$val = NULL
+throw <- function(obj) {
+ thrown_error$val = obj
+ stop("<mal_exception>")
+}
+get_error <- function(e) {
+ estr <- e$message
+ if (estr == "<mal_exception>") {
+ err <- thrown_error$val
+ thrown_error$val <- NULL
+ err
+ } else {
+ estr
+ }
+}
+
+# Scalars
+nil <- structure("malnil", class="nil")
+.nil_q <- function(obj) "nil" == class(obj)
+.true_q <- function(obj) "logical" == class(obj) && obj == TRUE
+.false_q <- function(obj) "logical" == class(obj) && obj == FALSE
+new.symbol <- function(name) structure(name, class="Symbol")
+
+.symbol_q <- function(obj) "Symbol" == class(obj)
+new.keyword <- function(name) concat("\u029e", name)
+.keyword_q <- function(obj) {
+ "character" == class(obj) && "\u029e" == substr(obj,1,1)
+}
+
+# Functions
+
+malfunc <- function(eval, ast, env, params) {
+ gen_env <- function(args) new.Env(env, params, args)
+ structure(list(eval=eval,
+ ast=ast,
+ env=env,
+ params=params,
+ gen_env=gen_env,
+ ismacro=FALSE), class="MalFunc")
+}
+.malfunc_q <- function(obj) "MalFunc" == class(obj)
+
+fapply <- function(mf, args) {
+ if (class(mf) == "MalFunc") {
+ ast <- mf$ast
+ env <- mf$gen_env(args)
+ mf$eval(ast, env)
+ } else {
+ #print(args)
+ do.call(mf,args)
+ }
+}
+
+# Lists
+new.list <- function(...) new.listl(list(...))
+new.listl <- function(lst) { class(lst) <- "List"; lst }
+.list_q <- function(obj) "List" == class(obj)
+
+# Vectors
+new.vector <- function(...) new.vectorl(list(...))
+new.vectorl <- function(lst) { class(lst) <- "Vector"; lst }
+.vector_q <- function(obj) "Vector" == class(obj)
+
+# Hash Maps
+new.hash_map <- function(...) new.hash_mapl(list(...))
+new.hash_mapl <- function(lst) {
+ .assoc(new.env(), lst)
+}
+.assoc <- function(src_hm, lst) {
+ hm <- .clone(src_hm)
+ if (length(lst) > 0) {
+ for(i in seq(1,length(lst),2)) {
+ hm[[lst[[i]]]] <- lst[[i+1]]
+ }
+ }
+ class(hm) <- "HashMap"
+ hm
+}
+.dissoc <- function(src_hm, lst) {
+ hm <- .clone(src_hm)
+ if (length(lst) > 0) {
+ for(k in lst) {
+ remove(list=c(k), envir=hm)
+ }
+ }
+ ls(hm)
+ class(hm) <- "HashMap"
+ hm
+}
+.hash_map_q <- function(obj) "HashMap" == class(obj)
+
+# Atoms
+new.atom <- function(val) {
+ atm <- new.env()
+ class(atm) <- "Atom"
+ atm$val <- .clone(val)
+ atm
+}
+.atom_q <- function(obj) "Atom" == class(obj)
diff --git a/racket/Makefile b/racket/Makefile
new file mode 100644
index 0000000..01cb12a
--- /dev/null
+++ b/racket/Makefile
@@ -0,0 +1,12 @@
+SOURCES_BASE = types.rkt reader.rkt printer.rkt
+SOURCES_LISP = env.rkt core.rkt stepA_mal.rkt
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+all:
+
+.PHONY: stats
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/racket/core.rkt b/racket/core.rkt
new file mode 100644
index 0000000..1cb41bf
--- /dev/null
+++ b/racket/core.rkt
@@ -0,0 +1,101 @@
+#lang racket
+
+(provide core_ns)
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt")
+
+(define (throw exc)
+ (raise (make-mal-exn "mal exception"
+ (current-continuation-marks)
+ exc)))
+
+;; Sequence functions
+(define conj
+ (lambda a
+ (if (vector? (first a))
+ (vector-append (first a) (list->vector (rest a)))
+ (append (reverse (rest a)) (first a)))))
+
+;; Meta functions
+(define (meta obj)
+ (cond [(malfunc? obj) (malfunc-meta obj)]
+ [else nil]))
+
+(define (with-meta obj m)
+ (cond [(malfunc? obj) (struct-copy malfunc obj [meta m])]
+ [else (raise "metadata not supported on type")]))
+
+;; Atom functions
+
+(define swap!
+ (lambda a
+ (let* ([atm (first a)]
+ [f (second a)]
+ [args (cons (atom-val atm) (rest (rest a)))]
+ [val (apply f args)])
+ (set-atom-val! atm val)
+ val)))
+
+(define core_ns
+ (hash
+ '= _equal?
+ 'throw throw
+
+ 'nil? _nil?
+ 'true? (lambda (x) (eq? x #t))
+ 'false? (lambda (x) (eq? x #f))
+ 'symbol (lambda (s) (if (symbol? s) s (string->symbol s)))
+ 'symbol? symbol?
+ 'keyword (lambda (s) (if (_keyword? s) s (_keyword s)))
+ 'keyword? _keyword?
+
+ 'pr-str (lambda a (pr_lst a #t " "))
+ 'str (lambda a (pr_lst a #f ""))
+ 'prn (lambda a (printf "~a~n" (pr_lst a #t " ")) nil)
+ 'println (lambda a (printf "~a~n" (pr_lst a #f " ")) nil)
+ 'read-string (lambda (s) (read_str s))
+ 'readline readline
+ 'slurp (lambda (f) (port->string (open-input-file f)))
+
+ '< <
+ '<= <=
+ '> >
+ '>= >=
+ '+ +
+ '- -
+ '* *
+ '/ /
+ 'time-ms (lambda () (round (current-inexact-milliseconds)))
+
+ 'list list
+ 'list? list?
+ 'vector vector
+ 'vector? vector?
+ 'hash-map hash
+ 'map? hash?
+ 'assoc _assoc
+ 'dissoc _dissoc
+ 'get _get
+ 'contains? dict-has-key?
+ 'keys hash-keys
+ 'vals hash-values
+
+ 'sequential? _sequential?
+ 'cons (lambda a (cons (first a) (_to_list (second a))))
+ 'concat (lambda a (apply append (map _to_list a)))
+ 'nth _nth
+ 'first _first
+ 'rest _rest
+ 'empty? _empty?
+ 'count _count
+ 'apply apply
+ 'map (lambda (f s) (_to_list (_map f s)))
+ 'conj conj
+
+ 'meta meta
+ 'with-meta with-meta
+ 'atom atom
+ 'atom? atom?
+ 'deref (lambda (a) (atom-val a))
+ 'reset! (lambda (a v) (set-atom-val! a v) v)
+ 'swap! swap!))
diff --git a/racket/env.rkt b/racket/env.rkt
new file mode 100644
index 0000000..8e47b63
--- /dev/null
+++ b/racket/env.rkt
@@ -0,0 +1,47 @@
+#lang racket
+
+(provide Env%)
+
+(require "types.rkt")
+
+(define Env%
+ (class object%
+ (init outer binds exprs)
+ (super-new)
+ (define _outer outer)
+ (define _binds (_to_list binds))
+ (define _exprs (_to_list exprs))
+ (define data (make-hash))
+ (let ([vargs (member '& _binds)])
+ (if vargs
+ (begin
+ (map (lambda (b e) (hash-set! data b e))
+ (drop-right _binds 2)
+ (take _exprs (- (length _binds) 2)))
+ (hash-set! data
+ (last _binds)
+ (drop _exprs (- (length _binds) 2))))
+ (map (lambda (b e) (hash-set! data b e))
+ _binds
+ _exprs)))
+
+ (define/public (set k v)
+ (hash-set! data k v)
+ v)
+ (define/public (find k)
+ (cond
+ [(hash-has-key? data k) this]
+ [(not (null? _outer)) (send _outer find k)]
+ [else null]))
+ (define/public (_get k)
+ (hash-ref data k))
+ (define/public (get k)
+ (let ([e (find k)])
+ (if (null? e)
+ (raise (string-append "'"
+ (symbol->string k)
+ "' not found"))
+ (send e _get k))))))
+
+
+
diff --git a/racket/printer.rkt b/racket/printer.rkt
new file mode 100644
index 0000000..07a8bb8
--- /dev/null
+++ b/racket/printer.rkt
@@ -0,0 +1,44 @@
+#lang racket
+
+(provide pr_str pr_lst)
+
+(require "types.rkt")
+
+(define (pr_str obj print_readably)
+ (let ([_r print_readably])
+ (cond
+ [(list? obj)
+ (string-join (map (lambda (o) (pr_str o _r)) obj)
+ " " #:before-first "(" #:after-last ")")]
+ [(vector? obj)
+ (string-join (map (lambda (o) (pr_str o _r)) (vector->list obj))
+ " " #:before-first "[" #:after-last "]")]
+ [(hash? obj)
+ (string-join (dict-map obj (lambda (k v)
+ (format "~a ~a"
+ (pr_str k _r)
+ (pr_str v _r))))
+ " " #:before-first "{" #:after-last "}")]
+ [(string? obj)
+ (if (regexp-match #px"^\u029e" obj)
+ (format ":~a" (substring obj 1))
+ (if _r
+ (format "\"~a\""
+ (string-replace
+ (string-replace
+ (string-replace obj "\\" "\\\\")
+ "\"" "\\\"")
+ "\n" "\\n"))
+ obj))]
+ [(number? obj) (number->string obj)]
+ [(symbol? obj) (symbol->string obj)]
+ [(atom? obj) (format "(atom ~a)" (atom-val obj))]
+ [(_nil? obj) "nil"]
+ [(eq? #t obj) "true"]
+ [(eq? #f obj) "false"]
+ [else (format "~a" obj)])))
+
+(define (pr_lst lst print_readably sep)
+ (string-join
+ (map (lambda (s) (pr_str s print_readably)) lst)
+ sep))
diff --git a/racket/reader.rkt b/racket/reader.rkt
new file mode 100644
index 0000000..6db2e67
--- /dev/null
+++ b/racket/reader.rkt
@@ -0,0 +1,85 @@
+#lang racket
+
+(provide read_str)
+
+(require "types.rkt")
+
+(define Reader%
+ (class object%
+ (init tokens)
+ (super-new)
+ (define toks tokens)
+ (define position 0)
+ (define/public (next)
+ (cond [(>= position (length toks)) null]
+ [else (begin
+ (set! position (+ 1 position))
+ (list-ref toks (- position 1)))]))
+ (define/public (peek)
+ (cond [(>= position (length toks)) null]
+ [else (list-ref toks position )]))))
+
+
+(define (tokenize str)
+ (filter-not (lambda (s) (or (equal? s "") (equal? (substring s 0 1) ";")))
+ (regexp-match* #px"[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)"
+ str #:match-select cadr)))
+
+(define (read_atom rdr)
+ (let ([token (send rdr next)])
+ (cond [(regexp-match #px"^-?[0-9]+$" token)
+ (string->number token)]
+ [(regexp-match #px"^-?[0-9][0-9.]*$" token)
+ (string->number token)]
+ [(regexp-match #px"^\".*\"$" token)
+ (string-replace
+ (string-replace
+ (substring token 1 (- (string-length token) 1))
+ "\\\"" "\"")
+ "\\n" "\n")]
+ [(regexp-match #px"^:" token) (_keyword (substring token 1))]
+ [(equal? "nil" token) nil]
+ [(equal? "true" token) #t]
+ [(equal? "false" token) #f]
+ [else (string->symbol token)])))
+
+(define (read_list_entries rdr end)
+ (let ([tok (send rdr peek)])
+ (cond
+ [(eq? tok '()) (raise (string-append "expected '" end "'"))]
+ [(equal? end tok) '()]
+ [else
+ (cons (read_form rdr) (read_list_entries rdr end))])))
+
+(define (read_list rdr start end)
+ (let ([token (send rdr next)])
+ (if (equal? start token)
+ (let ([lst (read_list_entries rdr end)])
+ (send rdr next)
+ lst)
+ (raise (string-append "expected '" start "'")))))
+
+(define (read_form rdr)
+ (let ([token (send rdr peek)])
+ (if (null? token)
+ (raise (make-blank-exn "blank line" (current-continuation-marks)))
+ (cond
+ [(equal? "'" token) (send rdr next) (list 'quote (read_form rdr))]
+ [(equal? "`" token) (send rdr next) (list 'quasiquote (read_form rdr))]
+ [(equal? "~" token) (send rdr next) (list 'unquote (read_form rdr))]
+ [(equal? "~@" token) (send rdr next) (list 'splice-unquote (read_form rdr))]
+ [(equal? "^" token) (send rdr next)
+ (let ([meta (read_form rdr)])
+ (list 'with-meta (read_form rdr) meta))]
+ [(equal? "@" token) (send rdr next) (list 'deref (read_form rdr))]
+
+ [(equal? ")" token) (raise "unexpected ')'")]
+ [(equal? "(" token) (read_list rdr "(" ")")]
+ [(equal? "]" token) (raise "unexpected ']'")]
+ [(equal? "[" token) (list->vector (read_list rdr "[" "]"))]
+ [(equal? "}" token) (raise "unexpected '}'")]
+ [(equal? "{" token) (apply hash (read_list rdr "{" "}"))]
+ [else (read_atom rdr)]))))
+
+(define (read_str str)
+ (read_form (new Reader% [tokens (tokenize str)])))
diff --git a/racket/readline.rkt b/racket/readline.rkt
new file mode 100644
index 0000000..7f92169
--- /dev/null
+++ b/racket/readline.rkt
@@ -0,0 +1,32 @@
+#lang racket
+
+(provide readline)
+
+(require (prefix-in readline: readline/readline))
+
+(require "types.rkt")
+
+(define history-loaded #f)
+(define HISTORY-FILE (format "~a/.mal-history" (find-system-path 'home-dir)))
+
+(define (load-history path)
+ (map
+ (lambda (line) (readline:add-history line))
+ (string-split
+ (port->string (open-input-file path))
+ #px"\n")))
+
+(define (readline prompt)
+ (when (not history-loaded)
+ (set! history-loaded #t)
+ (load-history HISTORY-FILE))
+ (let ([line (readline:readline prompt)])
+ (if (eq? eof line)
+ nil
+ (begin
+ (readline:add-history line)
+ (with-output-to-file
+ HISTORY-FILE
+ (lambda () (printf "~a~n" line))
+ #:exists 'append)
+ line))))
diff --git a/racket/step0_repl.rkt b/racket/step0_repl.rkt
new file mode 100755
index 0000000..d09d5ba
--- /dev/null
+++ b/racket/step0_repl.rkt
@@ -0,0 +1,27 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "types.rkt")
+
+;; read
+(define (READ str)
+ str)
+
+;; eval
+(define (EVAL ast env)
+ ast)
+
+;; print
+(define (PRINT exp)
+ exp)
+
+;; repl
+(define (rep str)
+ (PRINT (EVAL (READ str) "")))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (printf "~a~n" (rep line))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step1_read_print.rkt b/racket/step1_read_print.rkt
new file mode 100755
index 0000000..a5d8ac7
--- /dev/null
+++ b/racket/step1_read_print.rkt
@@ -0,0 +1,30 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (EVAL ast env)
+ ast)
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define (rep str)
+ (PRINT (EVAL (READ str) "")))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step2_eval.rkt b/racket/step2_eval.rkt
new file mode 100755
index 0000000..ce4d563
--- /dev/null
+++ b/racket/step2_eval.rkt
@@ -0,0 +1,49 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "types.rkt" "readline.rkt" "reader.rkt" "printer.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast)
+ (or (hash-ref env ast
+ (lambda () (raise (string-append "'"
+ (symbol->string ast)
+ "' not found")))))]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (apply f args))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env (hash '+ + '- - '* * '/ /))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step3_env.rkt b/racket/step3_env.rkt
new file mode 100755
index 0000000..fa735b8
--- /dev/null
+++ b/racket/step3_env.rkt
@@ -0,0 +1,61 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (apply f args))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env%
+ [outer null]
+ [binds '(+ - * /)]
+ [exprs (list + - * /)]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step4_if_fn_do.rkt b/racket/step4_if_fn_do.rkt
new file mode 100755
index 0000000..8401397
--- /dev/null
+++ b/racket/step4_if_fn_do.rkt
@@ -0,0 +1,82 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'do a0)
+ (last (eval-ast (rest ast) env))]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (apply f args))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+
+;; core.mal: defined using the language itself
+(rep "(def! not (fn* (a) (if a false true)))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step5_tco.rkt b/racket/step5_tco.rkt
new file mode 100755
index 0000000..0fbdf9c
--- /dev/null
+++ b/racket/step5_tco.rkt
@@ -0,0 +1,91 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+
+;; core.mal: defined using the language itself
+(rep "(def! not (fn* (a) (if a false true)))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step6_file.rkt b/racket/step6_file.rkt
new file mode 100755
index 0000000..627fb9a
--- /dev/null
+++ b/racket/step6_file.rkt
@@ -0,0 +1,97 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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) \")\")))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/step7_quote.rkt b/racket/step7_quote.rkt
new file mode 100755
index 0000000..2b7baff
--- /dev/null
+++ b/racket/step7_quote.rkt
@@ -0,0 +1,119 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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) \")\")))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/step8_macros.rkt b/racket/step8_macros.rkt
new file mode 100755
index 0000000..7016a12
--- /dev/null
+++ b/racket/step8_macros.rkt
@@ -0,0 +1,143 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (macro? ast env)
+ (and (list? ast)
+ (symbol? (first ast))
+ (not (equal? null (send env find (first ast))))
+ (let ([fn (send env get (first ast))])
+ (and (malfunc? fn) (malfunc-macro? fn)))))
+
+(define (macroexpand ast env)
+ (if (macro? ast env)
+ (let ([mac (malfunc-fn (send env get (first ast)))])
+ (macroexpand (apply mac (rest ast)) env))
+ ast))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([ast (macroexpand ast env)])
+ (if (not (list? ast))
+ ast
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'defmacro! a0)
+ (let* ([func (EVAL (_nth ast 2) env)]
+ [mac (struct-copy malfunc func [macro? #t])])
+ (send env set (_nth ast 1) mac))]
+ [(eq? 'macroexpand a0)
+ (macroexpand (_nth ast 1) env)]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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))))))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/step9_try.rkt b/racket/step9_try.rkt
new file mode 100755
index 0000000..434ec44
--- /dev/null
+++ b/racket/step9_try.rkt
@@ -0,0 +1,160 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (macro? ast env)
+ (and (list? ast)
+ (symbol? (first ast))
+ (not (equal? null (send env find (first ast))))
+ (let ([fn (send env get (first ast))])
+ (and (malfunc? fn) (malfunc-macro? fn)))))
+
+(define (macroexpand ast env)
+ (if (macro? ast env)
+ (let ([mac (malfunc-fn (send env get (first ast)))])
+ (macroexpand (apply mac (rest ast)) env))
+ ast))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ ;(printf "~a~n" (pr_str ast true))
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([ast (macroexpand ast env)])
+ (if (not (list? ast))
+ ast
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'defmacro! a0)
+ (let* ([func (EVAL (_nth ast 2) env)]
+ [mac (struct-copy malfunc func [macro? #t])])
+ (send env set (_nth ast 1) mac))]
+ [(eq? 'macroexpand a0)
+ (macroexpand (_nth ast 1) env)]
+ [(eq? 'try* a0)
+ (if (eq? 'catch* (_nth (_nth ast 2) 0))
+ (let ([efn (lambda (exc)
+ (EVAL (_nth (_nth ast 2) 2)
+ (new Env%
+ [outer env]
+ [binds (list (_nth (_nth ast 2) 1))]
+ [exprs (list exc)])))])
+ (with-handlers
+ ([mal-exn? (lambda (exc) (efn (mal-exn-val exc)))]
+ [string? (lambda (exc) (efn exc))]
+ [exn:fail? (lambda (exc) (efn (format "~a" exc)))])
+ (EVAL (_nth ast 1) env)))
+ (EVAL (_nth ast 1)))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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))))))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [mal-exn? (lambda (exc) (printf "Error: ~a~n"
+ (pr_str (mal-exn-val exc) true)))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/stepA_mal.rkt b/racket/stepA_mal.rkt
new file mode 100755
index 0000000..9b816cb
--- /dev/null
+++ b/racket/stepA_mal.rkt
@@ -0,0 +1,163 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (macro? ast env)
+ (and (list? ast)
+ (symbol? (first ast))
+ (not (equal? null (send env find (first ast))))
+ (let ([fn (send env get (first ast))])
+ (and (malfunc? fn) (malfunc-macro? fn)))))
+
+(define (macroexpand ast env)
+ (if (macro? ast env)
+ (let ([mac (malfunc-fn (send env get (first ast)))])
+ (macroexpand (apply mac (rest ast)) env))
+ ast))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ ;(printf "~a~n" (pr_str ast true))
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([ast (macroexpand ast env)])
+ (if (not (list? ast))
+ ast
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'defmacro! a0)
+ (let* ([func (EVAL (_nth ast 2) env)]
+ [mac (struct-copy malfunc func [macro? #t])])
+ (send env set (_nth ast 1) mac))]
+ [(eq? 'macroexpand a0)
+ (macroexpand (_nth ast 1) env)]
+ [(eq? 'try* a0)
+ (if (eq? 'catch* (_nth (_nth ast 2) 0))
+ (let ([efn (lambda (exc)
+ (EVAL (_nth (_nth ast 2) 2)
+ (new Env%
+ [outer env]
+ [binds (list (_nth (_nth ast 2) 1))]
+ [exprs (list exc)])))])
+ (with-handlers
+ ([mal-exn? (lambda (exc) (efn (mal-exn-val exc)))]
+ [string? (lambda (exc) (efn exc))]
+ [exn:fail? (lambda (exc) (efn (format "~a" exc)))])
+ (EVAL (_nth ast 1) env)))
+ (EVAL (_nth ast 1)))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; core.mal: defined using the language itself
+(rep "(def! *host-language* \"racket\")")
+(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))))))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [mal-exn? (lambda (exc) (printf "Error: ~a~n"
+ (pr_str (mal-exn-val exc) true)))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (begin
+ (rep "(println (str \"Mal [\" *host-language* \"]\"))")
+ (repl-loop))))
diff --git a/racket/types.rkt b/racket/types.rkt
new file mode 100644
index 0000000..6ca29e6
--- /dev/null
+++ b/racket/types.rkt
@@ -0,0 +1,110 @@
+#lang racket
+
+(provide blank-exn? make-blank-exn mal-exn? make-mal-exn mal-exn-val
+ malfunc malfunc? malfunc-fn
+ malfunc-ast malfunc-env malfunc-params malfunc-macro? malfunc-meta
+ _partition _equal? _printf
+ nil _nil? _keyword _keyword?
+ _to_list _sequential? _count _empty? _nth _first _rest _map
+ _assoc _dissoc _get
+ atom atom? atom-val set-atom-val!)
+
+(define-struct (blank-exn exn:fail:user) ())
+(define-struct (mal-exn exn:fail:user) [val])
+
+(define nil%
+ (class object%
+ (super-new)))
+
+(define nil (new nil%))
+
+(define (_nil? obj)
+ (eq? nil obj))
+
+(struct malfunc [fn ast env params macro? meta]
+ #:property prop:procedure (struct-field-index fn))
+
+;; General functions
+
+;; From: http://stackoverflow.com/questions/8725832/how-to-split-list-into-evenly-sized-chunks-in-racket-scheme/8731622#8731622
+(define (_partition n xs)
+ (if (null? xs)
+ '()
+ (let ((first-chunk (take xs n))
+ (rest (drop xs n)))
+ (cons first-chunk (_partition n rest)))))
+
+(define (_equal? a b)
+ (cond
+ [(and (list? a) (vector? b))
+ (equal? a (vector->list b))]
+ [(and (vector? a) (list? b))
+ (equal? (vector->list a) b)]
+ [else (equal? a b)]))
+
+;; printf with flush
+(define _printf (lambda a (apply printf a) (flush-output)))
+
+;; Keywords
+(define (_keyword str)
+ (string-append "\u029e" str))
+
+(define (_keyword? k)
+ (and (string? k) (regexp-match? #px"^\u029e" k)))
+
+
+;; Lists and vectors
+
+(define (_to_list a)
+ (if (vector? a) (vector->list a) a))
+
+(define (_sequential? seq)
+ (or (vector? seq) (list? seq)))
+
+(define (_count seq)
+ (cond [(_nil? seq) 0]
+ [(vector? seq) (vector-length seq)]
+ [else (length seq)]))
+
+(define (_empty? seq)
+ (eq? 0 (_count seq)))
+
+(define (_nth seq idx)
+ (cond [(>= idx (_count seq)) (raise "nth: index out of range")]
+ [(vector? seq) (vector-ref seq idx)]
+ [else (list-ref seq idx)]))
+
+(define (_first seq)
+ (cond [(vector? seq) (if (_empty? seq) nil (vector-ref seq 0))]
+ [else (if (_empty? seq) nil (list-ref seq 0))]))
+
+(define (_rest seq)
+ (cond [(vector? seq) (if (_empty? seq) '() (rest (vector->list seq)))]
+ [else (if (_empty? seq) '() (rest seq))]))
+
+(define (_map f seq)
+ (cond [(vector? seq) (vector-map f seq)]
+ [else (map f seq)]))
+
+;; Hash maps
+(define _assoc
+ (lambda args
+ (let ([new-hm (hash-copy (first args))]
+ [pairs (_partition 2 (rest args))])
+ (map (lambda (k_v)
+ (hash-set! new-hm (first k_v) (second k_v))) pairs)
+ new-hm)))
+
+(define _dissoc
+ (lambda args
+ (let ([new-hm (hash-copy (first args))])
+ (map (lambda (k) (hash-remove! new-hm k)) (rest args))
+ new-hm)))
+
+(define (_get hm k)
+ (cond [(_nil? hm) nil]
+ [(dict-has-key? hm k) (hash-ref hm k)]
+ [else nil]))
+
+;; Atoms
+(struct atom [val] #:mutable)
diff --git a/ruby/Makefile b/ruby/Makefile
index 71ab92c..2241a4e 100644
--- a/ruby/Makefile
+++ b/ruby/Makefile
@@ -1,7 +1,7 @@
TESTS =
SOURCES_BASE = mal_readline.rb types.rb reader.rb printer.rb
-SOURCES_LISP = env.rb core.rb stepA_more.rb
+SOURCES_LISP = env.rb core.rb stepA_mal.rb
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
#all: mal.rb
diff --git a/ruby/core.rb b/ruby/core.rb
index 6b127ba..b82bddc 100644
--- a/ruby/core.rb
+++ b/ruby/core.rb
@@ -1,6 +1,6 @@
require "readline"
-require "reader"
-require "printer"
+require_relative "reader"
+require_relative "printer"
$core_ns = {
:"=" => lambda {|a,b| a == b},
@@ -8,8 +8,10 @@ $core_ns = {
:nil? => lambda {|a| a == nil},
:true? => lambda {|a| a == true},
:false? => lambda {|a| a == false},
+ :symbol => lambda {|a| a.to_sym},
:symbol? => lambda {|a| a.is_a? Symbol},
- :symbol? => lambda {|a| a.is_a? Symbol},
+ :keyword => lambda {|a| "\u029e"+a},
+ :keyword? => lambda {|a| (a.is_a? String) && "\u029e" == a[0]},
:"pr-str" => lambda {|*a| a.map {|e| _pr_str(e, true)}.join(" ")},
:str => lambda {|*a| a.map {|e| _pr_str(e, false)}.join("")},
@@ -44,11 +46,11 @@ $core_ns = {
:sequential? => lambda {|a| sequential?(a)},
:cons => lambda {|a,b| List.new(b.clone.insert(0,a))},
:concat => lambda {|*a| List.new(a && a.reduce(:concat) || [])},
- :nth => lambda {|a,b| a[b]},
+ :nth => lambda {|a,b| raise "nth: index out of range" if b >= a.size; a[b]},
:first => lambda {|a| a[0]},
:rest => lambda {|a| List.new(a.size > 0 && a.drop(1) || [])},
:empty? => lambda {|a| a.size == 0},
- :count => lambda {|a| a.size},
+ :count => lambda {|a| return 0 if a == nil; a.size},
:conj => lambda {|*a| a[0].clone.conj(a.drop(1))},
:apply => lambda {|*a| a[0][*a[1..-2].concat(a[-1])]},
:map => lambda {|a,b| List.new(b.map {|e| a[e]})},
diff --git a/ruby/mal_readline.rb b/ruby/mal_readline.rb
index 63c5571..3799783 100644
--- a/ruby/mal_readline.rb
+++ b/ruby/mal_readline.rb
@@ -4,7 +4,7 @@ $history_loaded = false
$histfile = "#{ENV['HOME']}/.mal-history"
def _readline(prompt)
- if not $history_loaded
+ if !$history_loaded && File.exist?($histfile)
$history_loaded = true
File.readlines($histfile).each {|l| Readline::HISTORY.push(l.chomp)}
end
diff --git a/ruby/printer.rb b/ruby/printer.rb
index cfcd064..ef067a5 100644
--- a/ruby/printer.rb
+++ b/ruby/printer.rb
@@ -1,4 +1,4 @@
-require "types"
+require_relative "types"
def _pr_str(obj, print_readably=true)
_r = print_readably
@@ -12,7 +12,9 @@ def _pr_str(obj, print_readably=true)
obj.each{|k,v| ret.push(_pr_str(k,_r), _pr_str(v,_r))}
"{" + ret.join(" ") + "}"
when String
- if _r
+ if obj[0] == "\u029e"
+ ":" + obj[1..-1]
+ elsif _r
obj.inspect # escape special characters
else
obj
diff --git a/ruby/reader.rb b/ruby/reader.rb
index 1039105..badc6ec 100644
--- a/ruby/reader.rb
+++ b/ruby/reader.rb
@@ -1,4 +1,4 @@
-require "types"
+require_relative "types"
class Reader
def initialize(tokens)
@@ -31,7 +31,8 @@ def read_atom(rdr)
return case token
when /^-?[0-9]+$/ then token.to_i # integer
when /^-?[0-9][0-9.]*$/ then token.to_f # float
- when /^"/ then parse_str(token) # string
+ when /^".*"$/ then parse_str(token) # string
+ when /^:/ then "\u029e" + token[1..-1] # keyword
when "nil" then nil
when "true" then true
when "false" then false
@@ -56,7 +57,6 @@ def read_list(rdr, klass, start="(", last =")")
end
def read_form(rdr)
- token = rdr.peek
return case rdr.peek
when ";" then nil
when "'" then rdr.next; List.new [:quote, read_form(rdr)]
diff --git a/ruby/step0_repl.rb b/ruby/step0_repl.rb
index 9c03cfa..2f9e6a9 100644
--- a/ruby/step0_repl.rb
+++ b/ruby/step0_repl.rb
@@ -1,5 +1,4 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
+require_relative "mal_readline"
# read
def READ(str)
diff --git a/ruby/step1_read_print.rb b/ruby/step1_read_print.rb
index ded992a..ef416c3 100644
--- a/ruby/step1_read_print.rb
+++ b/ruby/step1_read_print.rb
@@ -1,8 +1,7 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
# read
def READ(str)
diff --git a/ruby/step2_eval.rb b/ruby/step2_eval.rb
index 50a135d..d2b7e1a 100644
--- a/ruby/step2_eval.rb
+++ b/ruby/step2_eval.rb
@@ -1,8 +1,7 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
# read
def READ(str)
diff --git a/ruby/step3_env.rb b/ruby/step3_env.rb
index 17126c5..ec8405b 100644
--- a/ruby/step3_env.rb
+++ b/ruby/step3_env.rb
@@ -1,9 +1,8 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
# read
def READ(str)
diff --git a/ruby/step4_if_fn_do.rb b/ruby/step4_if_fn_do.rb
index a93463b..151ecf6 100644
--- a/ruby/step4_if_fn_do.rb
+++ b/ruby/step4_if_fn_do.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
diff --git a/ruby/step5_tco.rb b/ruby/step5_tco.rb
index 38bb204..80be457 100644
--- a/ruby/step5_tco.rb
+++ b/ruby/step5_tco.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
diff --git a/ruby/step6_file.rb b/ruby/step6_file.rb
index 0c99cee..4eeca86 100644
--- a/ruby/step6_file.rb
+++ b/ruby/step6_file.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
diff --git a/ruby/step7_quote.rb b/ruby/step7_quote.rb
index 48385f1..23d9499 100644
--- a/ruby/step7_quote.rb
+++ b/ruby/step7_quote.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
diff --git a/ruby/step8_macros.rb b/ruby/step8_macros.rb
index 58adaea..488db12 100644
--- a/ruby/step8_macros.rb
+++ b/ruby/step8_macros.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
diff --git a/ruby/step9_interop.rb b/ruby/step9_try.rb
index 6d2cbe2..533853b 100644
--- a/ruby/step9_interop.rb
+++ b/ruby/step9_try.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
@@ -95,8 +94,21 @@ def EVAL(ast, env)
return env.set(a1, func)
when :macroexpand
return macroexpand(a1, env)
- when :"rb*"
- return eval(a1)
+ when :"try*"
+ begin
+ return EVAL(a1, env)
+ rescue Exception => exc
+ if exc.is_a? MalException
+ exc = exc.data
+ else
+ exc = exc.message
+ end
+ if a2 && a2[0] == :"catch*"
+ return EVAL(a2[2], Env.new(env, [a2[1]], [exc]))
+ else
+ raise esc
+ end
+ end
when :do
eval_ast(ast[1..-2], env)
ast = ast.last # Continue loop (TCO)
diff --git a/ruby/stepA_more.rb b/ruby/stepA_mal.rb
index 6123293..115fc8a 100644
--- a/ruby/stepA_more.rb
+++ b/ruby/stepA_mal.rb
@@ -1,10 +1,9 @@
-$: << File.expand_path(File.dirname(__FILE__))
-require "mal_readline"
-require "types"
-require "reader"
-require "printer"
-require "env"
-require "core"
+require_relative "mal_readline"
+require_relative "types"
+require_relative "reader"
+require_relative "printer"
+require_relative "env"
+require_relative "core"
# read
def READ(str)
@@ -96,7 +95,11 @@ def EVAL(ast, env)
when :macroexpand
return macroexpand(a1, env)
when :"rb*"
- return eval(a1)
+ res = eval(a1)
+ return case res
+ when Array; List.new res
+ else; res
+ end
when :"try*"
begin
return EVAL(a1, env)
diff --git a/ruby/tests/stepA_mal.mal b/ruby/tests/stepA_mal.mal
new file mode 100644
index 0000000..2d7efb8
--- /dev/null
+++ b/ruby/tests/stepA_mal.mal
@@ -0,0 +1,27 @@
+;; Testing basic ruby interop
+
+(rb* "7")
+;=>7
+
+(rb* "'7'")
+;=>"7"
+
+(rb* "[7,8,9]")
+;=>(7 8 9)
+
+(rb* "{\"abc\" => 789}")
+;=>{"abc" 789}
+
+(rb* "print 'hello\n'")
+; hello
+;=>nil
+
+(rb* "$foo=8;")
+(rb* "$foo")
+;=>8
+
+(rb* "['a','b','c'].map{|x| 'X'+x+'Y'}.join(' ')")
+;=>"XaY XbY XcY"
+
+(rb* "[1,2,3].map{|x| 1+x}")
+;=>(2 3 4)
diff --git a/ruby/types.rb b/ruby/types.rb
index 72d24d1..d64664b 100644
--- a/ruby/types.rb
+++ b/ruby/types.rb
@@ -1,4 +1,4 @@
-require "env"
+require_relative "env"
class MalException < StandardError
attr_reader :data
diff --git a/runtest-old.py b/runtest-old.py
new file mode 100755
index 0000000..aacd770
--- /dev/null
+++ b/runtest-old.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+
+import os, sys, re
+import argparse
+
+# http://pexpect.sourceforge.net/pexpect.html
+from pexpect import spawn, EOF, TIMEOUT
+
+# TODO: do we need to support '\n' too
+sep = "\r\n"
+rundir = None
+
+parser = argparse.ArgumentParser(
+ description="Run a test file against a Mal implementation")
+parser.add_argument('--rundir',
+ help="change to the directory before running tests")
+parser.add_argument('--start-timeout', default=10, type=int,
+ help="default timeout for initial prompt")
+parser.add_argument('--test-timeout', default=20, type=int,
+ help="default timeout for each individual test action")
+parser.add_argument('--pre-eval', default=None, type=str,
+ help="Mal code to evaluate prior to running the test")
+parser.add_argument('--redirect', action='store_true',
+ help="Run implementation in bash and redirect output to /dev/null")
+
+parser.add_argument('test_file', type=argparse.FileType('r'),
+ help="a test file formatted as with mal test data")
+parser.add_argument('mal_cmd', nargs="*",
+ help="Mal implementation command line. Use '--' to "
+ "specify a Mal command line with dashed options.")
+
+args = parser.parse_args(sys.argv[1:])
+test_data = args.test_file.read().split('\n')
+
+if args.rundir: os.chdir(args.rundir)
+
+if args.redirect:
+ # Redirect to try and force raw mode (no ASCII codes)
+ p = spawn('/bin/bash -c "' + " ".join(args.mal_cmd) + ' |tee /dev/null"')
+else:
+ p = spawn(args.mal_cmd[0], args.mal_cmd[1:])
+
+
+test_idx = 0
+def read_test(data):
+ global test_idx
+ form, output, ret = None, "", None
+ while data:
+ test_idx += 1
+ line = data.pop(0)
+ if re.match(r"^\s*$", line): # blank line
+ continue
+ elif line[0:3] == ";;;": # ignore comment
+ continue
+ elif line[0:2] == ";;": # output comment
+ print line[3:]
+ continue
+ elif line[0:2] == ";": # unexpected comment
+ print "Test data error at line %d:\n%s" % (test_idx, line)
+ return None, None, None, test_idx
+ form = line # the line is a form to send
+
+ # Now find the output and return value
+ while data:
+ line = data[0]
+ if line[0:3] == ";=>":
+ ret = line[3:].replace('\\r', '\r').replace('\\n', '\n')
+ test_idx += 1
+ data.pop(0)
+ break
+ elif line[0:2] == "; ":
+ output = output + line[2:] + sep
+ test_idx += 1
+ data.pop(0)
+ else:
+ ret = "*"
+ break
+ if ret: break
+
+ return form, output, ret, test_idx
+
+def assert_prompt(timeout):
+ # Wait for the initial prompt
+ idx = p.expect(['user> ', 'mal-user> ', EOF, TIMEOUT],
+ timeout=timeout)
+ if idx not in [0,1]:
+ print "Did not get 'user> ' or 'mal-user> ' prompt"
+ print " Got : %s" % repr(p.before)
+ sys.exit(1)
+
+
+# Wait for the initial prompt
+assert_prompt(args.start_timeout)
+
+# Send the pre-eval code if any
+if args.pre_eval:
+ sys.stdout.write("RUNNING pre-eval: %s" % args.pre_eval)
+ p.sendline(args.pre_eval)
+ assert_prompt(args.test_timeout)
+
+fail_cnt = 0
+
+while test_data:
+ form, out, ret, line_num = read_test(test_data)
+ if form == None:
+ 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)
+
+ p.sendline(form)
+ try:
+ idx = p.expect(['\r\nuser> ', '\nuser> ',
+ '\r\nmal-user> ', '\nmal-user> '],
+ timeout=args.test_timeout)
+ #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after))
+ if ret == "*" or p.before == expected:
+ print " -> SUCCESS"
+ else:
+ print " -> FAIL (line %d):" % line_num
+ print " Expected : %s" % repr(expected)
+ print " Got : %s" % repr(p.before)
+ fail_cnt += 1
+ except EOF:
+ print "Got EOF"
+ sys.exit(1)
+ except TIMEOUT:
+ print "Got TIMEOUT, received: %s" % repr(p.before)
+ sys.exit(1)
+
+if fail_cnt > 0:
+ print "FAILURES: %d" % fail_cnt
+ sys.exit(2)
+sys.exit(0)
diff --git a/runtest.py b/runtest.py
index aacd770..a7f0e44 100755
--- a/runtest.py
+++ b/runtest.py
@@ -1,13 +1,15 @@
#!/usr/bin/env python
import os, sys, re
-import argparse
+import argparse, time
-# http://pexpect.sourceforge.net/pexpect.html
-from pexpect import spawn, EOF, TIMEOUT
+import pty, signal, atexit
+from subprocess import Popen, STDOUT, PIPE
+from select import select
# TODO: do we need to support '\n' too
sep = "\r\n"
+#sep = "\n"
rundir = None
parser = argparse.ArgumentParser(
@@ -20,8 +22,8 @@ parser.add_argument('--test-timeout', default=20, type=int,
help="default timeout for each individual test action")
parser.add_argument('--pre-eval', default=None, type=str,
help="Mal code to evaluate prior to running the test")
-parser.add_argument('--redirect', action='store_true',
- help="Run implementation in bash and redirect output to /dev/null")
+parser.add_argument('--mono', action='store_true',
+ help="Use workarounds Mono/.Net Console misbehaviors")
parser.add_argument('test_file', type=argparse.FileType('r'),
help="a test file formatted as with mal test data")
@@ -29,16 +31,74 @@ parser.add_argument('mal_cmd', nargs="*",
help="Mal implementation command line. Use '--' to "
"specify a Mal command line with dashed options.")
+class Runner():
+ def __init__(self, args, mono=False):
+ #print "args: %s" % repr(args)
+ self.mono = mono
+
+ # Cleanup child process on exit
+ atexit.register(self.cleanup)
+
+ if mono:
+ self.p = Popen(args, bufsize=0,
+ stdin=PIPE, stdout=PIPE, stderr=STDOUT,
+ preexec_fn=os.setsid)
+ self.stdin = self.p.stdin
+ self.stdout = self.p.stdout
+ else:
+ # provide tty to get 'interactive' readline to work
+ master, slave = pty.openpty()
+ self.p = Popen(args, bufsize=0,
+ stdin=slave, stdout=slave, stderr=STDOUT,
+ preexec_fn=os.setsid)
+ self.stdin = os.fdopen(master, 'r+b', 0)
+ self.stdout = self.stdin
+
+ #print "started"
+ self.buf = ""
+ self.last_prompt = ""
+
+ def read_to_prompt(self, prompts, timeout):
+ end_time = time.time() + timeout
+ while time.time() < end_time:
+ [outs,_,_] = select([self.stdout], [], [], 1)
+ if self.stdout in outs:
+ new_data = self.stdout.read(1)
+ #print "new_data: '%s'" % new_data
+ if self.mono:
+ self.buf += new_data.replace("\n", "\r\n")
+ else:
+ self.buf += new_data
+ for prompt in prompts:
+ regexp = re.compile(prompt)
+ match = regexp.search(self.buf)
+ if match:
+ end = match.end()
+ buf = self.buf[0:end-len(prompt)]
+ self.buf = self.buf[end:]
+ self.last_prompt = prompt
+ return buf
+ return None
+
+ def writeline(self, str):
+ self.stdin.write(str + "\n")
+ if self.mono:
+ # Simulate echo
+ self.buf += str + "\r\n"
+
+ def cleanup(self):
+ #print "cleaning up"
+ if self.p:
+ os.killpg(self.p.pid, signal.SIGTERM)
+ self.p = None
+
+
args = parser.parse_args(sys.argv[1:])
test_data = args.test_file.read().split('\n')
if args.rundir: os.chdir(args.rundir)
-if args.redirect:
- # Redirect to try and force raw mode (no ASCII codes)
- p = spawn('/bin/bash -c "' + " ".join(args.mal_cmd) + ' |tee /dev/null"')
-else:
- p = spawn(args.mal_cmd[0], args.mal_cmd[1:])
+r = Runner(args.mal_cmd, mono=args.mono)
test_idx = 0
@@ -81,11 +141,13 @@ def read_test(data):
def assert_prompt(timeout):
# Wait for the initial prompt
- idx = p.expect(['user> ', 'mal-user> ', EOF, TIMEOUT],
- timeout=timeout)
- if idx not in [0,1]:
+ header = r.read_to_prompt(['user> ', 'mal-user> '], timeout=timeout)
+ if not header == None:
+ if header:
+ print "Started with:\n%s" % header
+ else:
print "Did not get 'user> ' or 'mal-user> ' prompt"
- print " Got : %s" % repr(p.before)
+ print " Got : %s" % repr(r.buf)
sys.exit(1)
@@ -95,7 +157,7 @@ assert_prompt(args.start_timeout)
# Send the pre-eval code if any
if args.pre_eval:
sys.stdout.write("RUNNING pre-eval: %s" % args.pre_eval)
- p.sendline(args.pre_eval)
+ p.write(args.pre_eval)
assert_prompt(args.test_timeout)
fail_cnt = 0
@@ -108,24 +170,21 @@ while test_data:
sys.stdout.flush()
expected = "%s%s%s%s" % (form, sep, out, ret)
- p.sendline(form)
+ r.writeline(form)
try:
- idx = p.expect(['\r\nuser> ', '\nuser> ',
- '\r\nmal-user> ', '\nmal-user> '],
- timeout=args.test_timeout)
+ res = r.read_to_prompt(['\r\nuser> ', '\nuser> ',
+ '\r\nmal-user> ', '\nmal-user> '],
+ timeout=args.test_timeout)
#print "%s,%s,%s" % (idx, repr(p.before), repr(p.after))
- if ret == "*" or p.before == expected:
+ if ret == "*" or res == expected:
print " -> SUCCESS"
else:
print " -> FAIL (line %d):" % line_num
print " Expected : %s" % repr(expected)
- print " Got : %s" % repr(p.before)
+ print " Got : %s" % repr(res)
fail_cnt += 1
- except EOF:
- print "Got EOF"
- sys.exit(1)
- except TIMEOUT:
- print "Got TIMEOUT, received: %s" % repr(p.before)
+ except:
+ print "Got Exception"
sys.exit(1)
if fail_cnt > 0:
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000..daf999d
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+
+name = "Mal"
+version = "0.0.1"
+authors = [ "Your name <you@example.com>" ]
+
+
+[dependencies.cadencemarseille-pcre]
+
+git = "https://github.com/kanaka/rust-pcre"
+
+
+#[profile.dev]
+#
+#debug = true
+
+
+[[bin]]
+name = "step0_repl"
+[[bin]]
+name = "step1_read_print"
+[[bin]]
+name = "step2_eval"
+[[bin]]
+name = "step3_env"
+[[bin]]
+name = "step4_if_fn_do"
+[[bin]]
+name = "step5_tco"
+[[bin]]
+name = "step6_file"
+[[bin]]
+name = "step7_quote"
+[[bin]]
+name = "step8_macros"
+[[bin]]
+name = "step9_try"
+[[bin]]
+name = "stepA_mal"
diff --git a/rust/Makefile b/rust/Makefile
new file mode 100644
index 0000000..da8a6c6
--- /dev/null
+++ b/rust/Makefile
@@ -0,0 +1,36 @@
+#####################
+
+SOURCES_BASE = src/types.rs src/readline.rs \
+ src/reader.rs src/printer.rs \
+ src/env.rs src/core.rs
+SOURCES_LISP = src/env.rs src/core.rs src/stepA_mal.rs
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#####################
+
+SRCS = step1_read_print.rs step2_eval.rs step3_env.rs \
+ step4_if_fn_do.rs step5_tco.rs step6_file.rs step7_quote.rs \
+ step8_macros.rs step9_try.rs stepA_mal.rs
+BINS = $(SRCS:%.rs=target/%)
+
+#####################
+
+all: mal
+
+mal: ${SOURCES_BASE} $(word $(words ${SOURCES_LISP}),${SOURCES_LISP})
+ cargo build
+ cp $(word $(words ${BINS}),${BINS}) $@
+
+#$(BINS): target/%: src/%.rs
+# cargo build $*
+
+clean:
+ cargo clean
+ rm -f mal
+
+.PHONY: stats stats-lisp
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/rust/src/core.rs b/rust/src/core.rs
new file mode 100644
index 0000000..2bc3c39
--- /dev/null
+++ b/rust/src/core.rs
@@ -0,0 +1,561 @@
+#![allow(dead_code)]
+
+extern crate time;
+use std::collections::HashMap;
+use std::io::File;
+
+use types::{MalVal,MalRet,err_val,err_str,err_string,
+ Nil,Int,Strn,List,Vector,Hash_Map,Func,MalFunc,Atom,
+ _nil,_true,_false,_int,string,
+ list,vector,listm,vectorm,hash_mapm,func,funcm,malfuncd};
+use types;
+use readline;
+use reader;
+use printer;
+
+// General functions
+fn equal_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to equal? call");
+ }
+ match a[0] == a[1] {
+ true => Ok(_true()),
+ false => Ok(_false()),
+ }
+}
+
+// Errors/Exceptions
+fn throw(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to throw call");
+ }
+ err_val(a[0].clone())
+}
+
+// String routines
+fn pr_str(a:Vec<MalVal>) -> MalRet {
+ Ok(string(printer::pr_list(&a, true, "", "", " ")))
+}
+
+fn str(a:Vec<MalVal>) -> MalRet {
+ Ok(string(printer::pr_list(&a, false, "", "", "")))
+}
+
+fn prn(a:Vec<MalVal>) -> MalRet {
+ println!("{}", printer::pr_list(&a, true, "", "", " "))
+ Ok(_nil())
+}
+
+fn println(a:Vec<MalVal>) -> MalRet {
+ println!("{}", printer::pr_list(&a, false, "", "", " "))
+ Ok(_nil())
+}
+
+fn readline(a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Strn(ref a0) => match readline::mal_readline(a0.as_slice()) {
+ Some(line) => Ok(string(line)),
+ None => err_val(_nil()),
+ },
+ _ => err_str("read_string called with non-string"),
+ }
+}
+
+fn read_string(a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Strn(ref a0) => reader::read_str(a0.to_string()),
+ _ => err_str("read_string called with non-string"),
+ }
+}
+
+fn slurp(a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Strn(ref a0) => {
+ match File::open(&Path::new(a0.as_slice())).read_to_string() {
+ Ok(s) => Ok(string(s)),
+ Err(e) => err_string(e.to_string()),
+ }
+ },
+ _ => err_str("slurp called with non-string"),
+ }
+}
+
+
+// Numeric functions
+fn int_op(f: |i:int,j:int|-> int, a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Int(a0) => match *a[1] {
+ Int(a1) => Ok(_int(f(a0,a1))),
+ _ => err_str("second arg must be an int"),
+ },
+ _ => err_str("first arg must be an int"),
+ }
+}
+
+fn bool_op(f: |i:int,j:int|-> bool, a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Int(a0) => match *a[1] {
+ Int(a1) => {
+ match f(a0,a1) {
+ true => Ok(_true()),
+ false => Ok(_false()),
+ }
+ },
+ _ => err_str("second arg must be an int"),
+ },
+ _ => err_str("first arg must be an int"),
+ }
+}
+
+pub fn add(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i+j }, a) }
+pub fn sub(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i-j }, a) }
+pub fn mul(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i*j }, a) }
+pub fn div(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i/j }, a) }
+
+pub fn lt (a:Vec<MalVal>) -> MalRet { bool_op(|i,j| { i<j }, a) }
+pub fn lte(a:Vec<MalVal>) -> MalRet { bool_op(|i,j| { i<=j }, a) }
+pub fn gt (a:Vec<MalVal>) -> MalRet { bool_op(|i,j| { i>j }, a) }
+pub fn gte(a:Vec<MalVal>) -> MalRet { bool_op(|i,j| { i>=j }, a) }
+
+#[allow(unused_variable)]
+pub fn time_ms(a:Vec<MalVal>) -> MalRet {
+ //let x = time::now();
+ let now = time::get_time();
+ let now_ms = (now.sec * 1000).to_int().unwrap() + (now.nsec.to_int().unwrap() / 1000000);
+ Ok(_int(now_ms))
+}
+
+
+// Hash Map functions
+pub fn assoc(a:Vec<MalVal>) -> MalRet {
+ if a.len() < 3 {
+ return err_str("Wrong arity to assoc call");
+ }
+ match *a[0] {
+ Hash_Map(ref hm,_) => {
+ types::_assoc(hm, a.slice(1,a.len()).to_vec())
+ },
+ Nil => {
+ types::hash_mapv(a.slice(1,a.len()).to_vec())
+ }
+ _ => return err_str("assoc onto non-hash map"),
+ }
+}
+
+pub fn dissoc(a:Vec<MalVal>) -> MalRet {
+ if a.len() < 2 {
+ return err_str("Wrong arity to dissoc call");
+ }
+ match *a[0] {
+ Hash_Map(ref hm,_) => {
+ types::_dissoc(hm, a.slice(1,a.len()).to_vec())
+ },
+ Nil => {
+ Ok(_nil())
+ }
+ _ => return err_str("dissoc onto non-hash map"),
+ }
+}
+
+pub fn get(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to get call");
+ }
+ let a0 = a[0].clone();
+ let hm: &HashMap<String,MalVal> = match *a0 {
+ Hash_Map(ref hm,_) => hm,
+ Nil => return Ok(_nil()),
+ _ => return err_str("get on non-hash map"),
+ };
+ match *a[1] {
+ Strn(ref key) => {
+ match hm.find_copy(key) {
+ Some(v) => Ok(v),
+ None => Ok(_nil()),
+ }
+ },
+ _ => return err_str("get with non-string key"),
+ }
+}
+
+pub fn contains_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to contains? call");
+ }
+ let a0 = a[0].clone();
+ let hm: &HashMap<String,MalVal> = match *a0 {
+ Hash_Map(ref hm,_) => hm,
+ Nil => return Ok(_false()),
+ _ => return err_str("contains? on non-hash map"),
+ };
+ match *a[1] {
+ Strn(ref key) => {
+ match hm.contains_key(key) {
+ true => Ok(_true()),
+ false => Ok(_false()),
+ }
+ },
+ _ => return err_str("contains? with non-string key"),
+ }
+}
+
+pub fn keys(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to keys call");
+ }
+ let a0 = a[0].clone();
+ let hm: &HashMap<String,MalVal> = match *a0 {
+ Hash_Map(ref hm,_) => hm,
+ Nil => return Ok(_nil()),
+ _ => return err_str("contains? on non-hash map"),
+ };
+ //if hm.len() == 0 { return Ok(_nil()); }
+ let mut keys = vec![];
+ for k in hm.keys() {
+ keys.push(string(k.to_string()));
+ }
+ Ok(list(keys))
+}
+
+pub fn vals(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to values call");
+ }
+ let a0 = a[0].clone();
+ let hm: &HashMap<String,MalVal> = match *a0 {
+ Hash_Map(ref hm,_) => hm,
+ Nil => return Ok(_nil()),
+ _ => return err_str("contains? on non-hash map"),
+ };
+ //if hm.len() == 0 { return Ok(_nil()); }
+ let mut vals = vec![];
+ for k in hm.values() {
+ vals.push(k.clone());
+ }
+ Ok(list(vals))
+}
+
+
+// Sequence functions
+pub fn cons(a:Vec<MalVal>) -> MalRet {
+ match *a[1] {
+ List(ref v,_) | Vector(ref v,_) => {
+ let mut new_v = v.clone();
+ new_v.insert(0, a[0].clone());
+ Ok(list(new_v))
+ },
+ _ => err_str("Second arg to cons not a sequence"),
+ }
+}
+
+pub fn concat(a:Vec<MalVal>) -> MalRet {
+ let mut new_v:Vec<MalVal> = vec![];
+ for lst in a.iter() {
+ match **lst {
+ List(ref l,_) | Vector(ref l,_) => {
+ new_v.push_all(l.as_slice());
+ },
+ _ => return err_str("concat called with non-sequence"),
+ }
+ }
+ Ok(list(new_v))
+}
+
+pub fn nth(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to nth call");
+ }
+ let a0 = a[0].clone();
+ let a1 = a[1].clone();
+ let seq = match *a0 {
+ List(ref v,_) | Vector(ref v,_) => v,
+ _ => return err_str("nth called with non-sequence"),
+ };
+ let idx = match *a1 {
+ Int(i) => {
+ match i.to_uint() {
+ Some(ui) => ui,
+ None => return Ok(_nil()),
+ }
+ },
+ _ => return err_str("nth called with non-integer index"),
+ };
+ if idx >= seq.len() {
+ return err_str("nth: index out of range")
+ } else {
+ Ok(seq[idx].clone())
+ }
+}
+
+pub fn first(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to first call");
+ }
+ let a0 = a[0].clone();
+ let seq = match *a0 {
+ List(ref v,_) | Vector(ref v,_) => v,
+ _ => return err_str("first called with non-sequence"),
+ };
+ if seq.len() == 0 {
+ Ok(_nil())
+ } else {
+ Ok(seq[0].clone())
+ }
+}
+
+pub fn rest(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to rest call");
+ }
+ let a0 = a[0].clone();
+ let seq = match *a0 {
+ List(ref v,_) | Vector(ref v,_) => v,
+ _ => return err_str("rest called with non-sequence"),
+ };
+ if seq.len() == 0 {
+ Ok(list(vec![]))
+ } else {
+ Ok(list(seq.slice(1,seq.len()).to_vec()))
+ }
+}
+
+pub fn empty_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to empty? call");
+ }
+ match *a[0].clone() {
+ List(ref v,_) | Vector(ref v,_) => {
+ match v.len() {
+ 0 => Ok(_true()),
+ _ => Ok(_false()),
+ }
+ },
+ _ => err_str("empty? called on non-sequence"),
+ }
+}
+
+pub fn count(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to count call");
+ }
+ match *a[0].clone() {
+ List(ref v,_) | Vector(ref v,_) => {
+ Ok(_int(v.len().to_int().unwrap()))
+ },
+ Nil => Ok(_int(0)),
+ _ => err_str("count called on non-sequence"),
+ }
+}
+
+pub fn apply(a:Vec<MalVal>) -> MalRet {
+ if a.len() < 2 {
+ return err_str("apply call needs 2 or more arguments");
+ }
+ let ref f = a[0];
+ let mut args = a.slice(1,a.len()-1).to_vec();
+ match *a[a.len()-1] {
+ List(ref v,_) | Vector(ref v,_) => {
+ args.push_all(v.as_slice());
+ f.apply(args)
+ },
+ _ => err_str("apply call with non-sequence"),
+ }
+}
+
+pub fn map(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to map call");
+ }
+ let mut results:Vec<MalVal> = vec![];
+ let ref f = a[0].clone();
+ let seq = a[1].clone();
+ match *seq {
+ List(ref v,_) | Vector(ref v,_) => {
+ for mv in v.iter() {
+ match f.apply(vec![mv.clone()]) {
+ Ok(res) => results.push(res),
+ Err(e) => return Err(e),
+ }
+ }
+ },
+ _ => return err_str("map call with non-sequence"),
+ }
+ Ok(list(results))
+}
+
+pub fn conj(a:Vec<MalVal>) -> MalRet {
+ if a.len() < 2 {
+ return err_str("Wrong arity to conj call");
+ }
+ let mut new_v:Vec<MalVal> = vec![];
+ match *a[0].clone() {
+ List(ref l,_) => {
+ new_v.push_all(l.as_slice());
+ for mv in a.iter().skip(1) {
+ new_v.insert(0,mv.clone());
+ }
+ Ok(list(new_v))
+ },
+ Vector(ref l,_) => {
+ new_v.push_all(l.as_slice());
+ for mv in a.iter().skip(1) {
+ new_v.push(mv.clone());
+ }
+ Ok(vector(new_v))
+ },
+ _ => return err_str("conj called with non-sequence"),
+ }
+}
+
+
+// Metadata functions
+fn with_meta(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to with-meta call");
+ }
+ let mv = a[0].clone();
+ let meta = a[1].clone();
+ match *mv {
+ List(ref v,_) => Ok(listm(v.clone(),meta)),
+ Vector(ref v,_) => Ok(vectorm(v.clone(),meta)),
+ Hash_Map(ref hm,_) => Ok(hash_mapm(hm.clone(),meta)),
+ MalFunc(ref mfd,_) => Ok(malfuncd(mfd.clone(),meta)),
+ Func(f,_) => Ok(funcm(f,meta)),
+ _ => err_str("type does not support metadata"),
+ }
+}
+
+fn meta(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to meta call");
+ }
+ match *a[0].clone() {
+ List(_,ref meta) |
+ Vector(_,ref meta) |
+ Hash_Map(_,ref meta) |
+ MalFunc(_,ref meta) |
+ Func(_,ref meta) => Ok(meta.clone()),
+ _ => err_str("type does not support metadata"),
+ }
+}
+
+// Atom functions
+fn deref(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to deref call");
+ }
+ match *a[0].clone() {
+ Atom(ref val) => {
+ let val_cell = val.borrow();
+ Ok(val_cell.clone())
+ },
+ _ => err_str("deref called on non-atom"),
+ }
+}
+
+fn reset_bang(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 2 {
+ return err_str("Wrong arity to map call");
+ }
+ let a1 = a[1].clone();
+ match *a[0].clone() {
+ Atom(ref val) => {
+ let mut val_cell = val.borrow_mut();
+ let atm_mv = val_cell.deref_mut();
+ *atm_mv = a1.clone();
+ Ok(a1)
+ },
+ _ => err_str("reset! called on non-atom"),
+ }
+}
+
+fn swap_bang(a:Vec<MalVal>) -> MalRet {
+ if a.len() < 2 {
+ return err_str("Wrong arity to swap_q call");
+ }
+ let f = a[1].clone();
+ match *a[0].clone() {
+ Atom(ref val) => {
+ let mut val_cell = val.borrow_mut();
+ let atm_mv = val_cell.deref_mut();
+ let mut args = a.slice(2,a.len()).to_vec();
+ args.insert(0, atm_mv.clone());
+ match f.apply(args) {
+ Ok(new_mv) => {
+ *atm_mv = new_mv.clone();
+ Ok(new_mv)
+ }
+ Err(e) => Err(e),
+ }
+ },
+ _ => err_str("swap! called on non-atom"),
+ }
+}
+
+
+pub fn ns() -> HashMap<String,MalVal> {
+ let mut ns: HashMap<String,MalVal> = HashMap::new();;
+
+ ns.insert("=".to_string(), func(equal_q));
+ ns.insert("throw".to_string(), func(throw));
+ ns.insert("nil?".to_string(), func(types::nil_q));
+ ns.insert("true?".to_string(), func(types::true_q));
+ ns.insert("false?".to_string(), func(types::false_q));
+ ns.insert("symbol".to_string(), func(types::_symbol));
+ ns.insert("symbol?".to_string(), func(types::symbol_q));
+ ns.insert("keyword".to_string(), func(types::_keyword));
+ ns.insert("keyword?".to_string(), func(types::keyword_q));
+
+ ns.insert("pr-str".to_string(), func(pr_str));
+ ns.insert("str".to_string(), func(str));
+ ns.insert("prn".to_string(), func(prn));
+ ns.insert("println".to_string(), func(println));
+ ns.insert("readline".to_string(), func(readline));
+ ns.insert("read-string".to_string(), func(read_string));
+ ns.insert("slurp".to_string(), func(slurp));
+
+ ns.insert("<".to_string(), func(lt));
+ ns.insert("<=".to_string(), func(lte));
+ ns.insert(">".to_string(), func(gt));
+ ns.insert(">=".to_string(), func(gte));
+ ns.insert("+".to_string(), func(add));
+ ns.insert("-".to_string(), func(sub));
+ ns.insert("*".to_string(), func(mul));
+ ns.insert("/".to_string(), func(div));
+ ns.insert("time-ms".to_string(), func(time_ms));
+
+ ns.insert("list".to_string(), func(types::listv));
+ ns.insert("list?".to_string(), func(types::list_q));
+ ns.insert("vector".to_string(), func(types::vectorv));
+ ns.insert("vector?".to_string(), func(types::vector_q));
+ ns.insert("hash-map".to_string(), func(types::hash_mapv));
+ ns.insert("map?".to_string(), func(types::hash_map_q));
+ ns.insert("assoc".to_string(), func(assoc));
+ ns.insert("dissoc".to_string(), func(dissoc));
+ ns.insert("get".to_string(), func(get));
+ ns.insert("contains?".to_string(), func(contains_q));
+ ns.insert("keys".to_string(), func(keys));
+ ns.insert("vals".to_string(), func(vals));
+
+ ns.insert("sequential?".to_string(), func(types::sequential_q));
+ ns.insert("cons".to_string(), func(cons));
+ ns.insert("concat".to_string(), func(concat));
+ ns.insert("empty?".to_string(), func(empty_q));
+ ns.insert("nth".to_string(), func(nth));
+ ns.insert("first".to_string(), func(first));
+ ns.insert("rest".to_string(), func(rest));
+ ns.insert("count".to_string(), func(count));
+ ns.insert("apply".to_string(), func(apply));
+ ns.insert("map".to_string(), func(map));
+ ns.insert("conj".to_string(), func(conj));
+
+ ns.insert("with-meta".to_string(), func(with_meta));
+ ns.insert("meta".to_string(), func(meta));
+ ns.insert("atom".to_string(), func(types::atom));
+ ns.insert("atom?".to_string(), func(types::atom_q));
+ ns.insert("deref".to_string(), func(deref));
+ ns.insert("reset!".to_string(), func(reset_bang));
+ ns.insert("swap!".to_string(), func(swap_bang));
+
+ return ns;
+}
diff --git a/rust/src/env.rs b/rust/src/env.rs
new file mode 100644
index 0000000..e9af154
--- /dev/null
+++ b/rust/src/env.rs
@@ -0,0 +1,118 @@
+#![allow(dead_code)]
+
+use std::rc::Rc;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt;
+
+use types::{MalVal,MalRet,Sym,List,Vector,_nil,list,err_string};
+
+struct EnvType {
+ data: HashMap<String,MalVal>,
+ outer: Option<Env>,
+}
+
+pub type Env = Rc<RefCell<EnvType>>;
+
+pub fn env_new(outer: Option<Env>) -> Env {
+ Rc::new(RefCell::new(EnvType{data: HashMap::new(), outer: outer}))
+}
+
+pub fn env_bind(env: &Env,
+ mbinds: MalVal,
+ mexprs: MalVal) -> Result<Env,String> {
+ let mut variadic = false;
+ match *mbinds {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ match *mexprs {
+ List(ref exprs,_) | Vector(ref exprs,_) => {
+ let mut it = binds.iter().enumerate();
+ for (i, b) in it {
+ match **b {
+ Sym(ref strn) => {
+ if *strn == "&".to_string() {
+ variadic = true;
+ break;
+ } else {
+ env_set(env, b.clone(), exprs[i].clone());
+ }
+ }
+ _ => return Err("non-symbol bind".to_string()),
+ }
+ }
+ if variadic {
+ let (i, sym) = it.next().unwrap();
+ match **sym {
+ Sym(_) => {
+ let rest = exprs.slice(i-1,exprs.len()).to_vec();
+ env_set(env, sym.clone(), list(rest));
+ }
+ _ => return Err("& bind to non-symbol".to_string()),
+ }
+ }
+ Ok(env.clone())
+ },
+ _ => Err("exprs must be a list".to_string()),
+ }
+ },
+ _ => Err("binds must be a list".to_string()),
+ }
+}
+
+pub fn env_find(env: Env, key: MalVal) -> Option<Env> {
+ match *key {
+ Sym(ref k) => {
+ if env.borrow().data.contains_key(k) {
+ Some(env)
+ } else {
+ match env.borrow().outer {
+ Some(ref e) => env_find(e.clone(), key.clone()),
+ None => None,
+ }
+ }
+ },
+ _ => None
+ }
+}
+
+pub fn env_root(env: &Env) -> Env {
+ match env.borrow().outer {
+ Some(ref ei) => env_root(ei),
+ None => env.clone(),
+ }
+}
+
+pub fn env_set(env: &Env, key: MalVal, val: MalVal) {
+ match *key {
+ Sym(ref k) => {
+ env.borrow_mut().data.insert(k.to_string(), val.clone());
+ },
+ _ => {},
+ }
+}
+
+pub fn env_get(env: Env, key: MalVal) -> MalRet {
+ match *key {
+ Sym(ref k) => {
+ match env_find(env, key.clone()) {
+ Some(e) => {
+ match e.borrow().data.find_copy(k) {
+ Some(v) => Ok(v),
+ None => Ok(_nil()),
+ }
+ },
+ None => err_string("'".to_string() + k.to_string() + "' not found".to_string()),
+ }
+ }
+ _ => err_string("env_get called with non-symbol key".to_string()),
+ }
+}
+
+impl fmt::Show for EnvType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.outer {
+ Some(ref o) => write!(f, "[{}/outer:{}]", self.data, o.borrow()),
+ _ => write!(f, "{}", self.data)
+ }
+ }
+}
diff --git a/rust/src/printer.rs b/rust/src/printer.rs
new file mode 100644
index 0000000..f46b66c
--- /dev/null
+++ b/rust/src/printer.rs
@@ -0,0 +1,45 @@
+use types::MalVal;
+
+pub fn escape_str(s: &str) -> String {
+ let mut escaped = String::new();
+ escaped.push('"');
+ for c in s.as_slice().chars() {
+ let _ = match c {
+ '"' => escaped.push_str("\\\""),
+ '\\' => escaped.push_str("\\\\"),
+ '\x08' => escaped.push_str("\\b"),
+ '\x0c' => escaped.push_str("\\f"),
+ '\n' => escaped.push_str("\\n"),
+ '\r' => escaped.push_str("\\r"),
+ '\t' => escaped.push_str("\\t"),
+ _ => escaped.push(c),
+ };
+ };
+
+ escaped.push('"');
+
+ escaped
+}
+
+pub fn unescape_str(s: &str) -> String {
+ let re1 = regex!(r#"\\""#);
+ let re2 = regex!(r#"\n"#);
+ re2.replace_all(re1.replace_all(s.as_slice(), "\"").as_slice(), "\n")
+}
+
+pub fn pr_list(lst: &Vec<MalVal>, pr: bool,
+ start: &str , end: &str, join: &str) -> String {
+ let mut first = true;
+ let mut res = String::new();
+ res.push_str(start);
+ for mv in lst.iter() {
+ if first {
+ first = false;
+ } else {
+ res.push_str(join);
+ }
+ res.push_str(mv.pr_str(pr).as_slice());
+ }
+ res.push_str(end);
+ res
+}
diff --git a/rust/src/reader.rs b/rust/src/reader.rs
new file mode 100644
index 0000000..d7b2b4c
--- /dev/null
+++ b/rust/src/reader.rs
@@ -0,0 +1,213 @@
+//#![feature(phase)]
+//#[phase(plugin)]
+//extern crate regex_macros;
+//extern crate regex;
+
+extern crate pcre;
+
+use types::{MalVal,MalRet,ErrString,ErrMalVal,
+ _nil,_true,_false,_int,symbol,string,list,vector,hash_mapv,
+ err_str,err_string,err_val};
+use self::pcre::Pcre;
+use super::printer::unescape_str;
+
+#[deriving(Show, Clone)]
+struct Reader {
+ tokens : Vec<String>,
+ position : uint,
+}
+
+impl Reader {
+ fn next(&mut self) -> Option<String> {
+ if self.position < self.tokens.len() {
+ self.position += 1;
+ Some(self.tokens[self.position-1].to_string())
+ } else {
+ None
+ }
+ }
+ fn peek(&self) -> Option<String> {
+ if self.position < self.tokens.len() {
+ Some(self.tokens[self.position].to_string())
+ } else {
+ None
+ }
+ }
+}
+
+fn tokenize(str :String) -> Vec<String> {
+ let mut results = vec![];
+
+ let re = match Pcre::compile(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"###) {
+ Err(_) => { fail!("failed to compile regex") },
+ Ok(re) => re
+ };
+
+ let mut it = re.matches(str.as_slice());
+ loop {
+ let opt_m = it.next();
+ if opt_m.is_none() { break; }
+ let m = opt_m.unwrap();
+ if m.group(1) == "" { break; }
+ if m.group(1).starts_with(";") { continue; }
+
+ results.push((*m.group(1)).to_string());
+ }
+ results
+}
+
+fn read_atom(rdr : &mut Reader) -> MalRet {
+ let otoken = rdr.next();
+ //println!("read_atom: {}", otoken);
+ if otoken.is_none() { return err_str("read_atom underflow"); }
+ let stoken = otoken.unwrap();
+ let token = stoken.as_slice();
+ if regex!(r"^-?[0-9]+$").is_match(token) {
+ let num : Option<int> = from_str(token);
+ Ok(_int(num.unwrap()))
+ } else if regex!(r#"^".*"$"#).is_match(token) {
+ let new_str = token.slice(1,token.len()-1);
+ Ok(string(unescape_str(new_str)))
+ } else if regex!(r#"^:"#).is_match(token) {
+ Ok(string("\u029e".to_string() + token.slice(1,token.len())))
+ } else if token == "nil" {
+ Ok(_nil())
+ } else if token == "true" {
+ Ok(_true())
+ } else if token == "false" {
+ Ok(_false())
+ } else {
+ Ok(symbol(token))
+ }
+}
+
+fn read_seq(rdr : &mut Reader, start: &str, end: &str) -> Result<Vec<MalVal>,String> {
+ let otoken = rdr.next();
+ if otoken.is_none() {
+ return Err("read_atom underflow".to_string());
+ }
+ let stoken = otoken.unwrap();
+ let token = stoken.as_slice();
+ if token != start {
+ return Err("expected '".to_string() + start.to_string() + "'".to_string());
+ }
+
+ let mut ast_vec : Vec<MalVal> = vec![];
+ loop {
+ let otoken = rdr.peek();
+ if otoken.is_none() {
+ return Err("expected '".to_string() + end.to_string() + "', got EOF".to_string());
+ }
+ let stoken = otoken.unwrap();
+ let token = stoken.as_slice();
+ if token == end { break; }
+
+ match read_form(rdr) {
+ Ok(mv) => ast_vec.push(mv),
+ Err(ErrString(es)) => return Err(es),
+ Err(ErrMalVal(_)) => return Err("read_seq exception".to_string()),
+ }
+ }
+ rdr.next();
+
+ Ok(ast_vec)
+}
+
+fn read_list(rdr : &mut Reader) -> MalRet {
+ match read_seq(rdr, "(", ")") {
+ Ok(seq) => Ok(list(seq)),
+ Err(es) => err_string(es),
+ }
+}
+
+fn read_vector(rdr : &mut Reader) -> MalRet {
+ match read_seq(rdr, "[", "]") {
+ Ok(seq) => Ok(vector(seq)),
+ Err(es) => err_string(es),
+ }
+}
+
+fn read_hash_map(rdr : &mut Reader) -> MalRet {
+ match read_seq(rdr, "{", "}") {
+ Ok(seq) => hash_mapv(seq),
+ Err(es) => err_string(es),
+ }
+}
+
+fn read_form(rdr : &mut Reader) -> MalRet {
+ let otoken = rdr.peek();
+ //println!("read_form: {}", otoken);
+ let stoken = otoken.unwrap();
+ let token = stoken.as_slice();
+ match token {
+ "'" => {
+ let _ = rdr.next();
+ match read_form(rdr) {
+ Ok(f) => Ok(list(vec![symbol("quote"), f])),
+ Err(e) => Err(e),
+ }
+ },
+ "`" => {
+ let _ = rdr.next();
+ match read_form(rdr) {
+ Ok(f) => Ok(list(vec![symbol("quasiquote"), f])),
+ Err(e) => Err(e),
+ }
+ },
+ "~" => {
+ let _ = rdr.next();
+ match read_form(rdr) {
+ Ok(f) => Ok(list(vec![symbol("unquote"), f])),
+ Err(e) => Err(e),
+ }
+ },
+ "~@" => {
+ let _ = rdr.next();
+ match read_form(rdr) {
+ Ok(f) => Ok(list(vec![symbol("splice-unquote"), f])),
+ Err(e) => Err(e),
+ }
+ },
+ "^" => {
+ let _ = rdr.next();
+ match read_form(rdr) {
+ Ok(meta) => {
+ match read_form(rdr) {
+ Ok(f) => Ok(list(vec![symbol("with-meta"), f, meta])),
+ Err(e) => Err(e),
+ }
+ },
+ Err(e) => Err(e),
+ }
+ },
+ "@" => {
+ let _ = rdr.next();
+ match read_form(rdr) {
+ Ok(f) => Ok(list(vec![symbol("deref"), f])),
+ Err(e) => Err(e),
+ }
+ },
+
+ ")" => err_str("unexected ')'"),
+ "(" => read_list(rdr),
+
+ "]" => err_str("unexected ']'"),
+ "[" => read_vector(rdr),
+
+ "}" => err_str("unexected '}'"),
+ "{" => read_hash_map(rdr),
+
+ _ => read_atom(rdr)
+ }
+}
+
+pub fn read_str(str :String) -> MalRet {
+ let tokens = tokenize(str);
+ if tokens.len() == 0 {
+ // any malval as the error slot means empty line
+ return err_val(_nil())
+ }
+ //println!("tokens: {}", tokens);
+ let rdr = &mut Reader{tokens: tokens, position: 0};
+ read_form(rdr)
+}
diff --git a/rust/src/readline.rs b/rust/src/readline.rs
new file mode 100644
index 0000000..17d1ed9
--- /dev/null
+++ b/rust/src/readline.rs
@@ -0,0 +1,76 @@
+// Based on: https://github.com/shaleh/rust-readline (MIT)
+extern crate libc;
+
+use std::c_str;
+
+use std::io::{File, Append, Write};
+use std::io::BufferedReader;
+
+mod ext_readline {
+ extern crate libc;
+ use self::libc::c_char;
+ #[link(name = "readline")]
+ extern {
+ pub fn add_history(line: *const c_char);
+ pub fn readline(p: *const c_char) -> *const c_char;
+ }
+}
+
+pub fn add_history(line: &str) {
+ unsafe {
+ ext_readline::add_history(line.to_c_str().as_ptr());
+ }
+}
+
+pub fn readline(prompt: &str) -> Option<String> {
+ let cprmt = prompt.to_c_str();
+ unsafe {
+ let ret = ext_readline::readline(cprmt.as_ptr());
+ if ret.is_null() { // user pressed Ctrl-D
+ None
+ }
+ else {
+ c_str::CString::new(ret, true).as_str().map(|ret| ret.to_string())
+ }
+ }
+}
+
+// --------------------------------------------
+
+static mut history_loaded : bool = false;
+static HISTORY_FILE : &'static str = "/home/joelm/.mal-history";
+
+fn load_history() {
+ unsafe {
+ if history_loaded { return; }
+ history_loaded = true;
+ }
+
+ let path = Path::new(HISTORY_FILE);
+ let mut file = BufferedReader::new(File::open(&path));
+ for line in file.lines() {
+ let rt: &[_] = &['\r', '\n'];
+ let line2 = line.unwrap();
+ let line3 = line2.as_slice().trim_right_chars(rt);
+ add_history(line3);
+ }
+}
+
+fn append_to_history(line: &str) {
+ let path = Path::new("/home/joelm/.mal-history");
+ let mut file = File::open_mode(&path, Append, Write);
+ let _ = file.write_line(line);
+}
+
+pub fn mal_readline (prompt: &str) -> Option<String> {
+ load_history();
+ let line = readline(prompt);
+ match line {
+ None => None,
+ _ => {
+ add_history(line.clone().unwrap().as_slice());
+ append_to_history(line.clone().unwrap().as_slice());
+ line
+ }
+ }
+}
diff --git a/rust/src/step0_repl.rs b/rust/src/step0_repl.rs
new file mode 100644
index 0000000..ac9cf24
--- /dev/null
+++ b/rust/src/step0_repl.rs
@@ -0,0 +1,25 @@
+use readline::mal_readline;
+mod readline;
+
+// read
+fn read(str: String) -> String {
+ str
+}
+
+// eval
+fn eval(ast: String) -> String {
+ ast
+}
+
+// print
+fn print(exp: String) -> String {
+ exp
+}
+
+fn main() {
+ loop {
+ let line = mal_readline("user> ");
+ match line { None => break, _ => () }
+ println!("{}", print(eval(read(line.unwrap()))));
+ }
+}
diff --git a/rust/src/step1_read_print.rs b/rust/src/step1_read_print.rs
new file mode 100644
index 0000000..3ce11e6
--- /dev/null
+++ b/rust/src/step1_read_print.rs
@@ -0,0 +1,52 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal};
+mod readline;
+mod types;
+mod env;
+mod reader;
+mod printer;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn eval(ast: MalVal) -> MalRet {
+ Ok(ast)
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: String) -> Result<String,MalError> {
+ match read(str) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step2_eval.rs b/rust/src/step2_eval.rs
new file mode 100644
index 0000000..2cf7897
--- /dev/null
+++ b/rust/src/step2_eval.rs
@@ -0,0 +1,129 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Int,Sym,List,Vector,Hash_Map,
+ _nil,_int,list,vector,hash_map,func};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env; // because types uses env
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn eval_ast(ast: MalVal, env: &HashMap<String,MalVal>) -> MalRet {
+ match *ast {
+ Sym(ref sym) => {
+ match env.find_copy(sym) {
+ Some(mv) => Ok(mv),
+ None => Ok(_nil()),
+ }
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ match eval(mv.clone(), env) {
+ Ok(mv) => ast_vec.push(mv),
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast.clone())
+ }
+ }
+}
+
+fn eval(ast: MalVal, env: &HashMap<String,MalVal>) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match eval_ast(ast, env) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ match *el {
+ List(ref args,_) => {
+ let ref f = args.clone()[0];
+ f.apply(args.slice(1,args.len()).to_vec())
+ }
+ _ => err_str("Invalid apply"),
+ }
+ }
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: &HashMap<String,MalVal>) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn int_op(f: |i:int,j:int|-> int, a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Int(a0) => match *a[1] {
+ Int(a1) => Ok(_int(f(a0,a1))),
+ _ => err_str("second arg must be an int"),
+ },
+ _ => err_str("first arg must be an int"),
+ }
+}
+fn add(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i+j }, a) }
+fn sub(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i-j }, a) }
+fn mul(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i*j }, a) }
+fn div(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i/j }, a) }
+
+fn main() {
+ let mut repl_env : HashMap<String,MalVal> = HashMap::new();
+ repl_env.insert("+".to_string(), func(add));
+ repl_env.insert("-".to_string(), func(sub));
+ repl_env.insert("*".to_string(), func(mul));
+ repl_env.insert("/".to_string(), func(div));
+
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), &repl_env) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step3_env.rs b/rust/src/step3_env.rs
new file mode 100644
index 0000000..b2d49cd
--- /dev/null
+++ b/rust/src/step3_env.rs
@@ -0,0 +1,204 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Int,Sym,List,Vector,Hash_Map,
+ symbol,_int,list,vector,hash_map,func};
+use env::{Env,env_new,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(ast: MalVal, env: Env) -> MalRet {
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ return eval(a2, let_env.clone());
+ },
+ _ => { // function call
+ return match eval_ast(ast, env) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ let ref f = args.clone()[0];
+ f.apply(args.slice(1,args.len()).to_vec())
+ }
+ };
+ },
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn int_op(f: |i:int,j:int|-> int, a:Vec<MalVal>) -> MalRet {
+ match *a[0] {
+ Int(a0) => match *a[1] {
+ Int(a1) => Ok(_int(f(a0,a1))),
+ _ => err_str("second arg must be an int"),
+ },
+ _ => err_str("first arg must be an int"),
+ }
+}
+fn add(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i+j }, a) }
+fn sub(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i-j }, a) }
+fn mul(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i*j }, a) }
+fn div(a:Vec<MalVal>) -> MalRet { int_op(|i,j| { i/j }, a) }
+
+fn main() {
+ let repl_env = env_new(None);
+ env_set(&repl_env, symbol("+"), func(add));
+ env_set(&repl_env, symbol("-"), func(sub));
+ env_set(&repl_env, symbol("*"), func(mul));
+ env_set(&repl_env, symbol("/"), func(div));
+
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step4_if_fn_do.rs b/rust/src/step4_if_fn_do.rs
new file mode 100644
index 0000000..92abf92
--- /dev/null
+++ b/rust/src/step4_if_fn_do.rs
@@ -0,0 +1,235 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,
+ symbol,_nil,list,vector,hash_map,malfunc};
+use env::{Env,env_new,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(ast: MalVal, env: Env) -> MalRet {
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ return eval(a2, let_env.clone());
+ },
+ "do" => {
+ let el = list(args.slice(1,args.len()).to_vec());
+ return match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(el) => {
+ match *el {
+ List(ref lst,_) => {
+ let ref last = lst[lst.len()-1];
+ return Ok(last.clone());
+ }
+ _ => return err_str("invalid do call"),
+ }
+ },
+ };
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ return eval(a3, env.clone());
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ return eval(a2, env.clone());
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ _ => { // function call
+ return match eval_ast(ast, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ let ref f = args.clone()[0];
+ f.apply(args.slice(1,args.len()).to_vec())
+ }
+ };
+ },
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step5_tco.rs b/rust/src/step5_tco.rs
new file mode 100644
index 0000000..9223cbf
--- /dev/null
+++ b/rust/src/step5_tco.rs
@@ -0,0 +1,257 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,Func,MalFunc,
+ symbol,_nil,list,vector,hash_map,malfunc};
+use env::{Env,env_new,env_bind,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
+ 'tco: loop {
+
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let ast2 = ast.clone();
+ let ast3 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast3);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ ast = a2;
+ env = let_env.clone();
+ continue 'tco;
+ },
+ "do" => {
+ let el = list(args.slice(1,args.len()-1).to_vec());
+ match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(_) => {
+ let ref last = args[args.len()-1];
+ ast = last.clone();
+ continue 'tco;
+ },
+ }
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ ast = a3;
+ env = env.clone();
+ continue 'tco;
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ ast = a2;
+ env = env.clone();
+ continue 'tco;
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ _ => { // function call
+ return match eval_ast(ast3, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ match *args.clone()[0] {
+ Func(f,_) => f(args.slice(1,args.len()).to_vec()),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args.slice(1,args.len()).to_vec());
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => {
+ ast = mfc.exp;
+ env = new_env;
+ continue 'tco;
+ },
+ Err(e) => err_str(e.as_slice()),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+ }
+ }
+ },
+ }
+
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step6_file.rs b/rust/src/step6_file.rs
new file mode 100644
index 0000000..1e87116
--- /dev/null
+++ b/rust/src/step6_file.rs
@@ -0,0 +1,293 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+use std::os;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,Func,MalFunc,
+ symbol,_nil,string,list,vector,hash_map,malfunc};
+use env::{Env,env_new,env_bind,env_root,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
+ 'tco: loop {
+
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let ast2 = ast.clone();
+ let ast3 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast3);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ ast = a2;
+ env = let_env.clone();
+ continue 'tco;
+ },
+ "do" => {
+ let el = list(args.slice(1,args.len()-1).to_vec());
+ match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(_) => {
+ let ref last = args[args.len()-1];
+ ast = last.clone();
+ continue 'tco;
+ },
+ }
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ ast = a3;
+ env = env.clone();
+ continue 'tco;
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ ast = a2;
+ env = env.clone();
+ continue 'tco;
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ "eval" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(exp) => {
+ ast = exp;
+ env = env_root(&env);
+ continue 'tco;
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ _ => { // function call
+ return match eval_ast(ast3, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ match *args.clone()[0] {
+ Func(f,_) => f(args.slice(1,args.len()).to_vec()),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args.slice(1,args.len()).to_vec());
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => {
+ ast = mfc.exp;
+ env = new_env;
+ continue 'tco;
+ },
+ Err(e) => err_str(e.as_slice()),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+ }
+ }
+ },
+ }
+
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+ // see eval() for definition of "eval"
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(vec![]));
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+ let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
+
+ // Invoked with command line arguments
+ let args = os::args();
+ if args.len() > 1 {
+ let mv_args = args.slice(2,args.len()).iter()
+ .map(|a| string(a.to_string()))
+ .collect::<Vec<MalVal>>();
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(mv_args));
+ let lf = "(load-file \"".to_string() + args[1] + "\")".to_string();
+ match rep(lf.as_slice(), repl_env.clone()) {
+ Ok(_) => {
+ os::set_exit_status(0);
+ return;
+ },
+ Err(str) => {
+ println!("Error: {}", str);
+ os::set_exit_status(1);
+ return;
+ },
+ }
+ }
+
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step7_quote.rs b/rust/src/step7_quote.rs
new file mode 100644
index 0000000..b113920
--- /dev/null
+++ b/rust/src/step7_quote.rs
@@ -0,0 +1,352 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+use std::os;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,Func,MalFunc,
+ symbol,_nil,string,list,vector,hash_map,malfunc};
+use env::{Env,env_new,env_bind,env_root,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn is_pair(x: MalVal) -> bool {
+ match *x {
+ List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0,
+ _ => false,
+ }
+}
+
+fn quasiquote(ast: MalVal) -> MalVal {
+ if !is_pair(ast.clone()) {
+ return list(vec![symbol("quote"), ast])
+ }
+
+ match *ast.clone() {
+ List(ref args,_) | Vector(ref args,_) => {
+ let ref a0 = args[0];
+ match **a0 {
+ Sym(ref s) => {
+ if s.to_string() == "unquote".to_string() {
+ let ref a1 = args[1];
+ return a1.clone();
+ }
+ },
+ _ => (),
+ }
+ if is_pair(a0.clone()) {
+ match **a0 {
+ List(ref a0args,_) | Vector(ref a0args,_) => {
+ let a00 = a0args[0].clone();
+ match *a00 {
+ Sym(ref s) => {
+ if s.to_string() == "splice-unquote".to_string() {
+ return list(vec![symbol("concat"),
+ a0args[1].clone(),
+ quasiquote(list(args.slice(1,args.len()).to_vec()))])
+ }
+ },
+ _ => (),
+ }
+ },
+ _ => (),
+ }
+ }
+ let rest = list(args.slice(1,args.len()).to_vec());
+ return list(vec![symbol("cons"),
+ quasiquote(a0.clone()),
+ quasiquote(rest)])
+ },
+ _ => _nil(), // should never reach
+ }
+}
+
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
+ 'tco: loop {
+
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+ let ast3 = ast2.clone();
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast3);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ ast = a2;
+ env = let_env.clone();
+ continue 'tco;
+ },
+ "quote" => {
+ return Ok((*args)[1].clone());
+ },
+ "quasiquote" => {
+ let a1 = (*args)[1].clone();
+ ast = quasiquote(a1);
+ continue 'tco;
+ },
+ "do" => {
+ let el = list(args.slice(1,args.len()-1).to_vec());
+ match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(_) => {
+ let ref last = args[args.len()-1];
+ ast = last.clone();
+ continue 'tco;
+ },
+ }
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ ast = a3;
+ env = env.clone();
+ continue 'tco;
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ ast = a2;
+ env = env.clone();
+ continue 'tco;
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ "eval" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(exp) => {
+ ast = exp;
+ env = env_root(&env);
+ continue 'tco;
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ _ => { // function call
+ return match eval_ast(ast3, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ match *args.clone()[0] {
+ Func(f,_) => f(args.slice(1,args.len()).to_vec()),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args.slice(1,args.len()).to_vec());
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => {
+ ast = mfc.exp;
+ env = new_env;
+ continue 'tco;
+ },
+ Err(e) => err_str(e.as_slice()),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+ }
+ }
+ },
+ }
+
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+ // see eval() for definition of "eval"
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(vec![]));
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+ let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
+
+ // Invoked with command line arguments
+ let args = os::args();
+ if args.len() > 1 {
+ let mv_args = args.slice(2,args.len()).iter()
+ .map(|a| string(a.to_string()))
+ .collect::<Vec<MalVal>>();
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(mv_args));
+ let lf = "(load-file \"".to_string() + args[1] + "\")".to_string();
+ match rep(lf.as_slice(), repl_env.clone()) {
+ Ok(_) => {
+ os::set_exit_status(0);
+ return;
+ },
+ Err(str) => {
+ println!("Error: {}", str);
+ os::set_exit_status(1);
+ return;
+ },
+ }
+ }
+
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step8_macros.rs b/rust/src/step8_macros.rs
new file mode 100644
index 0000000..7450de8
--- /dev/null
+++ b/rust/src/step8_macros.rs
@@ -0,0 +1,447 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+use std::os;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,Func,MalFunc,
+ symbol,_nil,string,list,vector,hash_map,malfunc,malfuncd};
+use env::{Env,env_new,env_bind,env_root,env_find,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn is_pair(x: MalVal) -> bool {
+ match *x {
+ List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0,
+ _ => false,
+ }
+}
+
+fn quasiquote(ast: MalVal) -> MalVal {
+ if !is_pair(ast.clone()) {
+ return list(vec![symbol("quote"), ast])
+ }
+
+ match *ast.clone() {
+ List(ref args,_) | Vector(ref args,_) => {
+ let ref a0 = args[0];
+ match **a0 {
+ Sym(ref s) => {
+ if s.to_string() == "unquote".to_string() {
+ let ref a1 = args[1];
+ return a1.clone();
+ }
+ },
+ _ => (),
+ }
+ if is_pair(a0.clone()) {
+ match **a0 {
+ List(ref a0args,_) | Vector(ref a0args,_) => {
+ let a00 = a0args[0].clone();
+ match *a00 {
+ Sym(ref s) => {
+ if s.to_string() == "splice-unquote".to_string() {
+ return list(vec![symbol("concat"),
+ a0args[1].clone(),
+ quasiquote(list(args.slice(1,args.len()).to_vec()))])
+ }
+ },
+ _ => (),
+ }
+ },
+ _ => (),
+ }
+ }
+ let rest = list(args.slice(1,args.len()).to_vec());
+ return list(vec![symbol("cons"),
+ quasiquote(a0.clone()),
+ quasiquote(rest)])
+ },
+ _ => _nil(), // should never reach
+ }
+}
+
+fn is_macro_call(ast: MalVal, env: Env) -> bool {
+ match *ast {
+ List(ref lst,_) => {
+ match *lst[0] {
+ Sym(_) => {
+ if env_find(env.clone(), lst[0].clone()).is_some() {
+ match env_get(env, lst[0].clone()) {
+ Ok(f) => {
+ match *f {
+ MalFunc(ref mfd,_) => {
+ mfd.is_macro
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+ } else {
+ false
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+}
+
+fn macroexpand(mut ast: MalVal, env: Env) -> MalRet {
+ while is_macro_call(ast.clone(), env.clone()) {
+ let ast2 = ast.clone();
+ let args = match *ast2 {
+ List(ref args,_) => args,
+ _ => break,
+ };
+ let ref a0 = args[0];
+ let mf = match **a0 {
+ Sym(_) => {
+ match env_get(env.clone(), a0.clone()) {
+ Ok(mf) => mf,
+ Err(e) => return Err(e),
+ }
+ },
+ _ => break,
+ };
+ match *mf {
+ MalFunc(_,_) => {
+ match mf.apply(args.slice(1,args.len()).to_vec()) {
+ Ok(r) => ast = r,
+ Err(e) => return Err(e),
+ }
+ },
+ _ => break,
+ }
+ }
+ Ok(ast)
+}
+
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
+ 'tco: loop {
+
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let mut ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match macroexpand(ast2, env.clone()) {
+ Ok(a) => {
+ ast2 = a;
+ },
+ Err(e) => return Err(e),
+ }
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+ let ast3 = ast2.clone();
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast3);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ ast = a2;
+ env = let_env.clone();
+ continue 'tco;
+ },
+ "quote" => {
+ return Ok((*args)[1].clone());
+ },
+ "quasiquote" => {
+ let a1 = (*args)[1].clone();
+ ast = quasiquote(a1);
+ continue 'tco;
+ },
+ "defmacro!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match eval(a2, env.clone()) {
+ Ok(r) => {
+ match *r {
+ MalFunc(ref mfd,_) => {
+ match *a1 {
+ Sym(_) => {
+ let mut new_mfd = mfd.clone();
+ new_mfd.is_macro = true;
+ let mf = malfuncd(new_mfd,_nil());
+ env_set(&env.clone(), a1.clone(), mf.clone());
+ return Ok(mf);
+ },
+ _ => return err_str("def! of non-symbol"),
+ }
+ },
+ _ => return err_str("def! of non-symbol"),
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "macroexpand" => {
+ let a1 = (*args)[1].clone();
+ return macroexpand(a1, env.clone())
+ },
+ "do" => {
+ let el = list(args.slice(1,args.len()-1).to_vec());
+ match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(_) => {
+ let ref last = args[args.len()-1];
+ ast = last.clone();
+ continue 'tco;
+ },
+ }
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ ast = a3;
+ env = env.clone();
+ continue 'tco;
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ ast = a2;
+ env = env.clone();
+ continue 'tco;
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ "eval" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(exp) => {
+ ast = exp;
+ env = env_root(&env);
+ continue 'tco;
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ _ => { // function call
+ return match eval_ast(ast3, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ match *args.clone()[0] {
+ Func(f,_) => f(args.slice(1,args.len()).to_vec()),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args.slice(1,args.len()).to_vec());
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => {
+ ast = mfc.exp;
+ env = new_env;
+ continue 'tco;
+ },
+ Err(e) => err_str(e.as_slice()),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+ }
+ }
+ },
+ }
+
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+ // see eval() for definition of "eval"
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(vec![]));
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+ let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
+ let _ = 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)))))))", repl_env.clone());
+ let _ = 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))))))))", repl_env.clone());
+
+ // Invoked with command line arguments
+ let args = os::args();
+ if args.len() > 1 {
+ let mv_args = args.slice(2,args.len()).iter()
+ .map(|a| string(a.to_string()))
+ .collect::<Vec<MalVal>>();
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(mv_args));
+ let lf = "(load-file \"".to_string() + args[1] + "\")".to_string();
+ match rep(lf.as_slice(), repl_env.clone()) {
+ Ok(_) => {
+ os::set_exit_status(0);
+ return;
+ },
+ Err(str) => {
+ println!("Error: {}", str);
+ os::set_exit_status(1);
+ return;
+ },
+ }
+ }
+
+ // repl loop
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/step9_try.rs b/rust/src/step9_try.rs
new file mode 100644
index 0000000..0f6bd88
--- /dev/null
+++ b/rust/src/step9_try.rs
@@ -0,0 +1,477 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+use std::os;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,Func,MalFunc,
+ symbol,_nil,string,list,vector,hash_map,malfunc,malfuncd};
+use env::{Env,env_new,env_bind,env_root,env_find,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn is_pair(x: MalVal) -> bool {
+ match *x {
+ List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0,
+ _ => false,
+ }
+}
+
+fn quasiquote(ast: MalVal) -> MalVal {
+ if !is_pair(ast.clone()) {
+ return list(vec![symbol("quote"), ast])
+ }
+
+ match *ast.clone() {
+ List(ref args,_) | Vector(ref args,_) => {
+ let ref a0 = args[0];
+ match **a0 {
+ Sym(ref s) => {
+ if s.to_string() == "unquote".to_string() {
+ let ref a1 = args[1];
+ return a1.clone();
+ }
+ },
+ _ => (),
+ }
+ if is_pair(a0.clone()) {
+ match **a0 {
+ List(ref a0args,_) | Vector(ref a0args,_) => {
+ let a00 = a0args[0].clone();
+ match *a00 {
+ Sym(ref s) => {
+ if s.to_string() == "splice-unquote".to_string() {
+ return list(vec![symbol("concat"),
+ a0args[1].clone(),
+ quasiquote(list(args.slice(1,args.len()).to_vec()))])
+ }
+ },
+ _ => (),
+ }
+ },
+ _ => (),
+ }
+ }
+ let rest = list(args.slice(1,args.len()).to_vec());
+ return list(vec![symbol("cons"),
+ quasiquote(a0.clone()),
+ quasiquote(rest)])
+ },
+ _ => _nil(), // should never reach
+ }
+}
+
+fn is_macro_call(ast: MalVal, env: Env) -> bool {
+ match *ast {
+ List(ref lst,_) => {
+ match *lst[0] {
+ Sym(_) => {
+ if env_find(env.clone(), lst[0].clone()).is_some() {
+ match env_get(env, lst[0].clone()) {
+ Ok(f) => {
+ match *f {
+ MalFunc(ref mfd,_) => {
+ mfd.is_macro
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+ } else {
+ false
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+}
+
+fn macroexpand(mut ast: MalVal, env: Env) -> MalRet {
+ while is_macro_call(ast.clone(), env.clone()) {
+ let ast2 = ast.clone();
+ let args = match *ast2 {
+ List(ref args,_) => args,
+ _ => break,
+ };
+ let ref a0 = args[0];
+ let mf = match **a0 {
+ Sym(_) => {
+ match env_get(env.clone(), a0.clone()) {
+ Ok(mf) => mf,
+ Err(e) => return Err(e),
+ }
+ },
+ _ => break,
+ };
+ match *mf {
+ MalFunc(_,_) => {
+ match mf.apply(args.slice(1,args.len()).to_vec()) {
+ Ok(r) => ast = r,
+ Err(e) => return Err(e),
+ }
+ },
+ _ => break,
+ }
+ }
+ Ok(ast)
+}
+
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
+ 'tco: loop {
+
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let mut ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match macroexpand(ast2, env.clone()) {
+ Ok(a) => {
+ ast2 = a;
+ },
+ Err(e) => return Err(e),
+ }
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+ let ast3 = ast2.clone();
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast3);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ ast = a2;
+ env = let_env.clone();
+ continue 'tco;
+ },
+ "quote" => {
+ return Ok((*args)[1].clone());
+ },
+ "quasiquote" => {
+ let a1 = (*args)[1].clone();
+ ast = quasiquote(a1);
+ continue 'tco;
+ },
+ "defmacro!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match eval(a2, env.clone()) {
+ Ok(r) => {
+ match *r {
+ MalFunc(ref mfd,_) => {
+ match *a1 {
+ Sym(_) => {
+ let mut new_mfd = mfd.clone();
+ new_mfd.is_macro = true;
+ let mf = malfuncd(new_mfd,_nil());
+ env_set(&env.clone(), a1.clone(), mf.clone());
+ return Ok(mf);
+ },
+ _ => return err_str("def! of non-symbol"),
+ }
+ },
+ _ => return err_str("def! of non-symbol"),
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "macroexpand" => {
+ let a1 = (*args)[1].clone();
+ return macroexpand(a1, env.clone())
+ },
+ "try*" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(res) => return Ok(res),
+ Err(err) => {
+ if args.len() < 3 { return Err(err); }
+ let a2 = (*args)[2].clone();
+ let cat = match *a2 {
+ List(ref cat,_) => cat,
+ _ => return err_str("invalid catch* clause"),
+ };
+ if cat.len() != 3 {
+ return err_str("wrong arity to catch* clause");
+ }
+ let c1 = (*cat)[1].clone();
+ match *c1 {
+ Sym(_) => {},
+ _ => return err_str("invalid catch* binding"),
+ };
+ let exc = match err {
+ ErrMalVal(mv) => mv,
+ ErrString(s) => string(s),
+ };
+ let bind_env = env_new(Some(env.clone()));
+ env_set(&bind_env, c1.clone(), exc);
+ let c2 = (*cat)[2].clone();
+ return eval(c2, bind_env);
+ },
+ };
+ }
+ "do" => {
+ let el = list(args.slice(1,args.len()-1).to_vec());
+ match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(_) => {
+ let ref last = args[args.len()-1];
+ ast = last.clone();
+ continue 'tco;
+ },
+ }
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ ast = a3;
+ env = env.clone();
+ continue 'tco;
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ ast = a2;
+ env = env.clone();
+ continue 'tco;
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ "eval" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(exp) => {
+ ast = exp;
+ env = env_root(&env);
+ continue 'tco;
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ _ => { // function call
+ return match eval_ast(ast3, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ match *args.clone()[0] {
+ Func(f,_) => f(args.slice(1,args.len()).to_vec()),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args.slice(1,args.len()).to_vec());
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => {
+ ast = mfc.exp;
+ env = new_env;
+ continue 'tco;
+ },
+ Err(e) => err_str(e.as_slice()),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+ }
+ }
+ },
+ }
+
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+ // see eval() for definition of "eval"
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(vec![]));
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+ let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
+ let _ = 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)))))))", repl_env.clone());
+ let _ = 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))))))))", repl_env.clone());
+
+ // Invoked with command line arguments
+ let args = os::args();
+ if args.len() > 1 {
+ let mv_args = args.slice(2,args.len()).iter()
+ .map(|a| string(a.to_string()))
+ .collect::<Vec<MalVal>>();
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(mv_args));
+ let lf = "(load-file \"".to_string() + args[1] + "\")".to_string();
+ match rep(lf.as_slice(), repl_env.clone()) {
+ Ok(_) => {
+ os::set_exit_status(0);
+ return;
+ },
+ Err(str) => {
+ println!("Error: {}", str);
+ os::set_exit_status(1);
+ return;
+ },
+ }
+ }
+
+ // repl loop
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/stepA_mal.rs b/rust/src/stepA_mal.rs
new file mode 100644
index 0000000..8e30867
--- /dev/null
+++ b/rust/src/stepA_mal.rs
@@ -0,0 +1,479 @@
+// support precompiled regexes in reader.rs
+#![feature(phase)]
+#[phase(plugin)]
+extern crate regex_macros;
+extern crate regex;
+
+use std::collections::HashMap;
+use std::os;
+
+use types::{MalVal,MalRet,MalError,ErrString,ErrMalVal,err_str,
+ Nil,False,Sym,List,Vector,Hash_Map,Func,MalFunc,
+ symbol,_nil,string,list,vector,hash_map,malfunc,malfuncd};
+use env::{Env,env_new,env_bind,env_root,env_find,env_set,env_get};
+mod readline;
+mod types;
+mod reader;
+mod printer;
+mod env;
+mod core;
+
+// read
+fn read(str: String) -> MalRet {
+ reader::read_str(str)
+}
+
+// eval
+fn is_pair(x: MalVal) -> bool {
+ match *x {
+ List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0,
+ _ => false,
+ }
+}
+
+fn quasiquote(ast: MalVal) -> MalVal {
+ if !is_pair(ast.clone()) {
+ return list(vec![symbol("quote"), ast])
+ }
+
+ match *ast.clone() {
+ List(ref args,_) | Vector(ref args,_) => {
+ let ref a0 = args[0];
+ match **a0 {
+ Sym(ref s) => {
+ if s.to_string() == "unquote".to_string() {
+ let ref a1 = args[1];
+ return a1.clone();
+ }
+ },
+ _ => (),
+ }
+ if is_pair(a0.clone()) {
+ match **a0 {
+ List(ref a0args,_) | Vector(ref a0args,_) => {
+ let a00 = a0args[0].clone();
+ match *a00 {
+ Sym(ref s) => {
+ if s.to_string() == "splice-unquote".to_string() {
+ return list(vec![symbol("concat"),
+ a0args[1].clone(),
+ quasiquote(list(args.slice(1,args.len()).to_vec()))])
+ }
+ },
+ _ => (),
+ }
+ },
+ _ => (),
+ }
+ }
+ let rest = list(args.slice(1,args.len()).to_vec());
+ return list(vec![symbol("cons"),
+ quasiquote(a0.clone()),
+ quasiquote(rest)])
+ },
+ _ => _nil(), // should never reach
+ }
+}
+
+fn is_macro_call(ast: MalVal, env: Env) -> bool {
+ match *ast {
+ List(ref lst,_) => {
+ match *lst[0] {
+ Sym(_) => {
+ if env_find(env.clone(), lst[0].clone()).is_some() {
+ match env_get(env, lst[0].clone()) {
+ Ok(f) => {
+ match *f {
+ MalFunc(ref mfd,_) => {
+ mfd.is_macro
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+ } else {
+ false
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+}
+
+fn macroexpand(mut ast: MalVal, env: Env) -> MalRet {
+ while is_macro_call(ast.clone(), env.clone()) {
+ let ast2 = ast.clone();
+ let args = match *ast2 {
+ List(ref args,_) => args,
+ _ => break,
+ };
+ let ref a0 = args[0];
+ let mf = match **a0 {
+ Sym(_) => {
+ match env_get(env.clone(), a0.clone()) {
+ Ok(mf) => mf,
+ Err(e) => return Err(e),
+ }
+ },
+ _ => break,
+ };
+ match *mf {
+ MalFunc(_,_) => {
+ match mf.apply(args.slice(1,args.len()).to_vec()) {
+ Ok(r) => ast = r,
+ Err(e) => return Err(e),
+ }
+ },
+ _ => break,
+ }
+ }
+ Ok(ast)
+}
+
+fn eval_ast(ast: MalVal, env: Env) -> MalRet {
+ let ast2 = ast.clone();
+ match *ast2 {
+ //match *ast {
+ Sym(_) => {
+ env_get(env.clone(), ast)
+ },
+ List(ref a,_) | Vector(ref a,_) => {
+ let mut ast_vec : Vec<MalVal> = vec![];
+ for mv in a.iter() {
+ let mv2 = mv.clone();
+ match eval(mv2, env.clone()) {
+ Ok(mv) => { ast_vec.push(mv); },
+ Err(e) => { return Err(e); },
+ }
+ }
+ Ok(match *ast { List(_,_) => list(ast_vec),
+ _ => vector(ast_vec) })
+ },
+ Hash_Map(ref hm,_) => {
+ let mut new_hm: HashMap<String,MalVal> = HashMap::new();
+ for (key, value) in hm.iter() {
+ match eval(value.clone(), env.clone()) {
+ Ok(mv) => { new_hm.insert(key.to_string(), mv); },
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(hash_map(new_hm))
+ },
+ _ => {
+ Ok(ast)
+ }
+ }
+}
+
+fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
+ 'tco: loop {
+
+ //println!("eval: {}, {}", ast, env.borrow());
+ //println!("eval: {}", ast);
+ let mut ast2 = ast.clone();
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return eval_ast(ast2, env),
+ }
+
+ // apply list
+ match macroexpand(ast2, env.clone()) {
+ Ok(a) => {
+ ast2 = a;
+ },
+ Err(e) => return Err(e),
+ }
+ match *ast2 {
+ List(_,_) => (), // continue
+ _ => return Ok(ast2),
+ }
+ let ast3 = ast2.clone();
+
+ let (args, a0sym) = match *ast2 {
+ List(ref args,_) => {
+ if args.len() == 0 {
+ return Ok(ast3);
+ }
+ let ref a0 = *args[0];
+ match *a0 {
+ Sym(ref a0sym) => (args, a0sym.as_slice()),
+ _ => (args, "__<fn*>__"),
+ }
+ },
+ _ => return err_str("Expected list"),
+ };
+
+ match a0sym {
+ "def!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ let res = eval(a2, env.clone());
+ match res {
+ Ok(r) => {
+ match *a1 {
+ Sym(_) => {
+ env_set(&env.clone(), a1.clone(), r.clone());
+ return Ok(r);
+ },
+ _ => {
+ return err_str("def! of non-symbol")
+ }
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "let*" => {
+ let let_env = env_new(Some(env.clone()));
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match *a1 {
+ List(ref binds,_) | Vector(ref binds,_) => {
+ let mut it = binds.iter();
+ while it.len() >= 2 {
+ let b = it.next().unwrap();
+ let exp = it.next().unwrap();
+ match **b {
+ Sym(_) => {
+ match eval(exp.clone(), let_env.clone()) {
+ Ok(r) => {
+ env_set(&let_env, b.clone(), r);
+ },
+ Err(e) => {
+ return Err(e);
+ },
+ }
+ },
+ _ => {
+ return err_str("let* with non-symbol binding");
+ },
+ }
+ }
+ },
+ _ => return err_str("let* with non-list bindings"),
+ }
+ ast = a2;
+ env = let_env.clone();
+ continue 'tco;
+ },
+ "quote" => {
+ return Ok((*args)[1].clone());
+ },
+ "quasiquote" => {
+ let a1 = (*args)[1].clone();
+ ast = quasiquote(a1);
+ continue 'tco;
+ },
+ "defmacro!" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ match eval(a2, env.clone()) {
+ Ok(r) => {
+ match *r {
+ MalFunc(ref mfd,_) => {
+ match *a1 {
+ Sym(_) => {
+ let mut new_mfd = mfd.clone();
+ new_mfd.is_macro = true;
+ let mf = malfuncd(new_mfd,_nil());
+ env_set(&env.clone(), a1.clone(), mf.clone());
+ return Ok(mf);
+ },
+ _ => return err_str("def! of non-symbol"),
+ }
+ },
+ _ => return err_str("def! of non-symbol"),
+ }
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ "macroexpand" => {
+ let a1 = (*args)[1].clone();
+ return macroexpand(a1, env.clone())
+ },
+ "try*" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(res) => return Ok(res),
+ Err(err) => {
+ if args.len() < 3 { return Err(err); }
+ let a2 = (*args)[2].clone();
+ let cat = match *a2 {
+ List(ref cat,_) => cat,
+ _ => return err_str("invalid catch* clause"),
+ };
+ if cat.len() != 3 {
+ return err_str("wrong arity to catch* clause");
+ }
+ let c1 = (*cat)[1].clone();
+ match *c1 {
+ Sym(_) => {},
+ _ => return err_str("invalid catch* binding"),
+ };
+ let exc = match err {
+ ErrMalVal(mv) => mv,
+ ErrString(s) => string(s),
+ };
+ let bind_env = env_new(Some(env.clone()));
+ env_set(&bind_env, c1.clone(), exc);
+ let c2 = (*cat)[2].clone();
+ return eval(c2, bind_env);
+ },
+ };
+ }
+ "do" => {
+ let el = list(args.slice(1,args.len()-1).to_vec());
+ match eval_ast(el, env.clone()) {
+ Err(e) => return Err(e),
+ Ok(_) => {
+ let ref last = args[args.len()-1];
+ ast = last.clone();
+ continue 'tco;
+ },
+ }
+ },
+ "if" => {
+ let a1 = (*args)[1].clone();
+ let cond = eval(a1, env.clone());
+ match cond {
+ Err(e) => return Err(e),
+ Ok(c) => match *c {
+ False | Nil => {
+ if args.len() >= 4 {
+ let a3 = (*args)[3].clone();
+ ast = a3;
+ env = env.clone();
+ continue 'tco;
+ } else {
+ return Ok(_nil());
+ }
+ },
+ _ => {
+ let a2 = (*args)[2].clone();
+ ast = a2;
+ env = env.clone();
+ continue 'tco;
+ },
+ }
+ }
+ },
+ "fn*" => {
+ let a1 = (*args)[1].clone();
+ let a2 = (*args)[2].clone();
+ return Ok(malfunc(eval, a2, env.clone(), a1, _nil()));
+ },
+ "eval" => {
+ let a1 = (*args)[1].clone();
+ match eval(a1, env.clone()) {
+ Ok(exp) => {
+ ast = exp;
+ env = env_root(&env);
+ continue 'tco;
+ },
+ Err(e) => return Err(e),
+ }
+ },
+ _ => { // function call
+ return match eval_ast(ast3, env.clone()) {
+ Err(e) => Err(e),
+ Ok(el) => {
+ let args = match *el {
+ List(ref args,_) => args,
+ _ => return err_str("Invalid apply"),
+ };
+ match *args.clone()[0] {
+ Func(f,_) => f(args.slice(1,args.len()).to_vec()),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args.slice(1,args.len()).to_vec());
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => {
+ ast = mfc.exp;
+ env = new_env;
+ continue 'tco;
+ },
+ Err(e) => err_str(e.as_slice()),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+ }
+ }
+ },
+ }
+
+ }
+}
+
+// print
+fn print(exp: MalVal) -> String {
+ exp.pr_str(true)
+}
+
+fn rep(str: &str, env: Env) -> Result<String,MalError> {
+ match read(str.to_string()) {
+ Err(e) => Err(e),
+ Ok(ast) => {
+ //println!("read: {}", ast);
+ match eval(ast, env) {
+ Err(e) => Err(e),
+ Ok(exp) => Ok(print(exp)),
+ }
+ }
+ }
+}
+
+fn main() {
+ // core.rs: defined using rust
+ let repl_env = env_new(None);
+ for (k, v) in core::ns().into_iter() {
+ env_set(&repl_env, symbol(k.as_slice()), v);
+ }
+ // see eval() for definition of "eval"
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(vec![]));
+
+ // core.mal: defined using the language itself
+ let _ = rep("(def! *host-language* \"rust\")", repl_env.clone());
+ let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
+ let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
+ let _ = 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)))))))", repl_env.clone());
+ let _ = 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))))))))", repl_env.clone());
+
+ // Invoked with command line arguments
+ let args = os::args();
+ if args.len() > 1 {
+ let mv_args = args.slice(2,args.len()).iter()
+ .map(|a| string(a.to_string()))
+ .collect::<Vec<MalVal>>();
+ env_set(&repl_env, symbol("*ARGV*".as_slice()), list(mv_args));
+ let lf = "(load-file \"".to_string() + args[1] + "\")".to_string();
+ match rep(lf.as_slice(), repl_env.clone()) {
+ Ok(_) => {
+ os::set_exit_status(0);
+ return;
+ },
+ Err(str) => {
+ println!("Error: {}", str);
+ os::set_exit_status(1);
+ return;
+ },
+ }
+ }
+
+ // repl loop
+ let _ = rep("(println (str \"Mal [\" *host-language* \"]\"))", repl_env.clone());
+ loop {
+ let line = readline::mal_readline("user> ");
+ match line { None => break, _ => () }
+ match rep(line.unwrap().as_slice(), repl_env.clone()) {
+ Ok(str) => println!("{}", str),
+ Err(ErrMalVal(_)) => (), // Blank line
+ Err(ErrString(s)) => println!("Error: {}", s),
+ }
+ }
+}
diff --git a/rust/src/types.rs b/rust/src/types.rs
new file mode 100644
index 0000000..141c3db
--- /dev/null
+++ b/rust/src/types.rs
@@ -0,0 +1,405 @@
+#![allow(dead_code)]
+
+use std::rc::Rc;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt;
+use super::printer::{escape_str,pr_list};
+use super::env::{Env,env_new,env_bind};
+
+#[deriving(Clone)]
+#[allow(non_camel_case_types)]
+pub enum MalType {
+ Nil,
+ True,
+ False,
+ Int(int),
+ Strn(String),
+ Sym(String),
+ List(Vec<MalVal>, MalVal),
+ Vector(Vec<MalVal>, MalVal),
+ Hash_Map(HashMap<String, MalVal>, MalVal),
+ Func(fn(Vec<MalVal>) -> MalRet, MalVal),
+ //Func(fn(&[MalVal]) -> MalRet),
+ //Func(|Vec<MalVal>|:'a -> MalRet),
+ MalFunc(MalFuncData, MalVal),
+ Atom(RefCell<MalVal>),
+}
+
+pub type MalVal = Rc<MalType>;
+
+#[deriving(Show)]
+pub enum MalError {
+ ErrString(String),
+ ErrMalVal(MalVal),
+}
+
+pub type MalRet = Result<MalVal,MalError>;
+
+
+pub fn err_string(s: String) -> MalRet {
+ Err(ErrString(s))
+}
+
+pub fn err_str(s: &str) -> MalRet {
+ Err(ErrString(s.to_string()))
+}
+
+pub fn err_val(mv: MalVal) -> MalRet {
+ Err(ErrMalVal(mv))
+}
+
+/*
+pub enum MalRet {
+ Val(MalVal),
+ MalErr(MalVal),
+ StringErr(String),
+}
+*/
+
+
+#[deriving(Clone)]
+pub struct MalFuncData {
+ pub eval: fn(MalVal, Env) -> MalRet,
+ pub exp: MalVal,
+ pub env: Env,
+ pub params: MalVal,
+ pub is_macro: bool,
+}
+
+impl MalType {
+ pub fn pr_str(&self, print_readably: bool) -> String {
+ let _r = print_readably;
+ let mut res = String::new();
+ match *self {
+ Nil => res.push_str("nil"),
+ True => res.push_str("true"),
+ False => res.push_str("false"),
+ Int(v) => res.push_str(v.to_string().as_slice()),
+ Sym(ref v) => res.push_str((*v).as_slice()),
+ Strn(ref v) => {
+ if v.as_slice().starts_with("\u029e") {
+ res.push_str(":");
+ res.push_str(v.as_slice().slice(2,v.len()))
+ } else if print_readably {
+ res.push_str(escape_str((*v).as_slice()).as_slice())
+ } else {
+ res.push_str(v.as_slice())
+ }
+ },
+ List(ref v,_) => {
+ res = pr_list(v, _r, "(", ")", " ")
+ },
+ Vector(ref v,_) => {
+ res = pr_list(v, _r, "[", "]", " ")
+ },
+ Hash_Map(ref v,_) => {
+ let mut first = true;
+ res.push_str("{");
+ for (key, value) in v.iter() {
+ if first { first = false; } else { res.push_str(" "); }
+ if key.as_slice().starts_with("\u029e") {
+ res.push_str(":");
+ res.push_str(key.as_slice().slice(2,key.len()))
+ } else if print_readably {
+ res.push_str(escape_str(key.as_slice()).as_slice())
+ } else {
+ res.push_str(key.as_slice())
+ }
+ res.push_str(" ");
+ res.push_str(value.pr_str(_r).as_slice());
+ }
+ res.push_str("}")
+ },
+ // TODO: better native function representation
+ Func(_,_) => {
+ res.push_str(format!("#<function ...>").as_slice())
+ },
+ MalFunc(ref mf,_) => {
+ res.push_str(format!("(fn* {} {})", mf.params, mf.exp).as_slice())
+ },
+ Atom(ref v) => {
+ res = format!("(atom {})", v.borrow());
+ },
+ };
+ res
+ }
+
+ pub fn apply(&self, args:Vec<MalVal>) -> MalRet {
+ match *self {
+ Func(f,_) => f(args),
+ MalFunc(ref mf,_) => {
+ let mfc = mf.clone();
+ let alst = list(args);
+ let new_env = env_new(Some(mfc.env.clone()));
+ match env_bind(&new_env, mfc.params, alst) {
+ Ok(_) => (mfc.eval)(mfc.exp, new_env),
+ Err(e) => err_string(e),
+ }
+ },
+ _ => err_str("attempt to call non-function"),
+ }
+
+ }
+}
+
+impl PartialEq for MalType {
+ fn eq(&self, other: &MalType) -> bool {
+ match (self, other) {
+ (&Nil, &Nil) |
+ (&True, &True) |
+ (&False, &False) => true,
+ (&Int(ref a), &Int(ref b)) => a == b,
+ (&Strn(ref a), &Strn(ref b)) => a == b,
+ (&Sym(ref a), &Sym(ref b)) => a == b,
+ (&List(ref a,_), &List(ref b,_)) |
+ (&Vector(ref a,_), &Vector(ref b,_)) |
+ (&List(ref a,_), &Vector(ref b,_)) |
+ (&Vector(ref a,_), &List(ref b,_)) => a == b,
+ (&Hash_Map(ref a,_), &Hash_Map(ref b,_)) => a == b,
+ // TODO: fix this
+ (&Func(_,_), &Func(_,_)) => false,
+ (&MalFunc(_,_), &MalFunc(_,_)) => false,
+ _ => return false,
+ }
+ }
+}
+
+impl fmt::Show for MalType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.pr_str(true))
+ }
+}
+
+
+// Scalars
+pub fn _nil() -> MalVal { Rc::new(Nil) }
+pub fn nil_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to nil? call");
+ }
+ match *a[0].clone() {
+ Nil => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+pub fn _true() -> MalVal { Rc::new(True) }
+pub fn true_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to true? call");
+ }
+ match *a[0].clone() {
+ True => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+pub fn _false() -> MalVal { Rc::new(False) }
+pub fn false_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to false? call");
+ }
+ match *a[0].clone() {
+ False => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+pub fn _int(i: int) -> MalVal { Rc::new(Int(i)) }
+
+
+// Symbols
+pub fn symbol(strn: &str) -> MalVal { Rc::new(Sym(strn.to_string())) }
+pub fn _symbol(a: Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to symbol call");
+ }
+ match *a[0].clone() {
+ Strn(ref s) => {
+ Ok(Rc::new(Sym(s.to_string())))
+ },
+ _ => return err_str("symbol called on non-string"),
+ }
+}
+pub fn symbol_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to symbol? call");
+ }
+ match *a[0].clone() {
+ Sym(_) => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+// Keywords
+pub fn _keyword(a: Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to keyword call");
+ }
+ match *a[0].clone() {
+ Strn(ref s) => {
+ Ok(Rc::new(Strn("\u029e".to_string() + s.to_string())))
+ },
+ _ => return err_str("keyword called on non-string"),
+ }
+}
+pub fn keyword_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to keyword? call");
+ }
+ match *a[0].clone() {
+ Strn(ref s) => {
+ if s.as_slice().starts_with("\u029e") {
+ Ok(_true())
+ } else {
+ Ok(_false())
+ }
+ },
+ _ => Ok(_false()),
+ }
+}
+
+
+// Strings
+pub fn strn(strn: &str) -> MalVal { Rc::new(Strn(strn.to_string())) }
+pub fn string(strn: String) -> MalVal { Rc::new(Strn(strn)) }
+
+// Lists
+pub fn list(seq: Vec<MalVal>) -> MalVal { Rc::new(List(seq,_nil())) }
+pub fn listm(seq: Vec<MalVal>, meta: MalVal) -> MalVal {
+ Rc::new(List(seq,meta))
+}
+pub fn listv(seq:Vec<MalVal>) -> MalRet { Ok(list(seq)) }
+pub fn list_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to list? call");
+ }
+ match *a[0].clone() {
+ List(_,_) => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+// Vectors
+pub fn vector(seq: Vec<MalVal>) -> MalVal { Rc::new(Vector(seq,_nil())) }
+pub fn vectorm(seq: Vec<MalVal>, meta: MalVal) -> MalVal {
+ Rc::new(Vector(seq,meta))
+}
+pub fn vectorv(seq: Vec<MalVal>) -> MalRet { Ok(vector(seq)) }
+pub fn vector_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to vector? call");
+ }
+ match *a[0].clone() {
+ Vector(_,_) => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+// Hash Maps
+pub fn hash_map(hm: HashMap<String,MalVal>) -> MalVal {
+ Rc::new(Hash_Map(hm,_nil()))
+}
+pub fn hash_mapm(hm: HashMap<String,MalVal>, meta: MalVal) -> MalVal {
+ Rc::new(Hash_Map(hm,meta))
+}
+pub fn _assoc(hm: &HashMap<String,MalVal>, a:Vec<MalVal>) -> MalRet {
+ if a.len() % 2 == 1 {
+ return err_str("odd number of hash-map keys/values");
+ }
+ let mut new_hm = hm.clone();
+ let mut it = a.iter();
+ loop {
+ let k = match it.next() {
+ Some(mv) => match *mv.clone() {
+ Strn(ref s) => s.to_string(),
+ _ => return err_str("key is not a string in hash-map call"),
+ },
+ None => break,
+ };
+ let v = it.next().unwrap();
+ new_hm.insert(k, v.clone());
+ }
+ Ok(Rc::new(Hash_Map(new_hm,_nil())))
+}
+pub fn _dissoc(hm: &HashMap<String,MalVal>, a:Vec<MalVal>) -> MalRet {
+ let mut new_hm = hm.clone();
+ let mut it = a.iter();
+ loop {
+ let k = match it.next() {
+ Some(mv) => match *mv.clone() {
+ Strn(ref s) => s.to_string(),
+ _ => return err_str("key is not a string in hash-map call"),
+ },
+ None => break,
+ };
+ new_hm.remove(&k);
+ }
+ Ok(Rc::new(Hash_Map(new_hm,_nil())))
+}
+pub fn hash_mapv(seq: Vec<MalVal>) -> MalRet {
+ let new_hm: HashMap<String,MalVal> = HashMap::new();
+ _assoc(&new_hm, seq)
+}
+pub fn hash_map_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to map? call");
+ }
+ match *a[0].clone() {
+ Hash_Map(_,_) => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+
+// Functions
+pub fn func(f: fn(Vec<MalVal>) -> MalRet) -> MalVal {
+ Rc::new(Func(f, _nil()))
+}
+pub fn funcm(f: fn(Vec<MalVal>) -> MalRet, meta: MalVal) -> MalVal {
+ Rc::new(Func(f, meta))
+}
+pub fn malfunc(eval: fn(MalVal, Env) -> MalRet,
+ exp: MalVal,
+ env: Env,
+ params: MalVal,
+ meta: MalVal) -> MalVal {
+ Rc::new(MalFunc(MalFuncData{eval: eval,
+ exp: exp,
+ env: env,
+ params: params,
+ is_macro: false},meta))
+}
+pub fn malfuncd(mfd: MalFuncData, meta: MalVal) -> MalVal {
+ Rc::new(MalFunc(mfd,meta))
+}
+
+
+// Atoms
+pub fn atom_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to atom? call");
+ }
+ match *a[0].clone() {
+ Atom(_) => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
+pub fn atom(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to atom call");
+ }
+ Ok(Rc::new(Atom(RefCell::new(a[0].clone()))))
+}
+
+
+// General functions
+pub fn sequential_q(a:Vec<MalVal>) -> MalRet {
+ if a.len() != 1 {
+ return err_str("Wrong arity to sequential? call");
+ }
+ match *a[0].clone() {
+ List(_,_) | Vector(_,_) => Ok(_true()),
+ _ => Ok(_false()),
+ }
+}
diff --git a/scala/Makefile b/scala/Makefile
new file mode 100644
index 0000000..29dd8aa
--- /dev/null
+++ b/scala/Makefile
@@ -0,0 +1,20 @@
+TESTS =
+
+SOURCES_BASE = types.scala reader.scala printer.scala
+SOURCES_LISP = env.scala core.scala stepA_mal.scala
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#all: mal.scala
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ ruby $@ || exit 1; \
diff --git a/scala/build.sbt b/scala/build.sbt
new file mode 100644
index 0000000..d2a2802
--- /dev/null
+++ b/scala/build.sbt
@@ -0,0 +1,15 @@
+lazy val root = (project in file(".")).
+ settings(
+ name := "mal",
+ version := "0.1",
+ scalaVersion := "2.11.4"
+ )
+
+// Suppress message for command line execution
+
+onLoadMessage := ""
+
+showSuccess := false
+
+logLevel in runMain := Level.Warn
+
diff --git a/scala/core.scala b/scala/core.scala
new file mode 100644
index 0000000..3317d08
--- /dev/null
+++ b/scala/core.scala
@@ -0,0 +1,265 @@
+import scala.collection.mutable
+import scala.io.Source
+
+import types.{MalList, _list, _list_Q,
+ MalVector, _vector, _vector_Q,
+ MalHashMap, _hash_map_Q, _hash_map,
+ Func, MalFunction}
+import printer._pr_list
+
+object core {
+ def mal_throw(a: List[Any]) = {
+ throw new types.MalException(printer._pr_str(a(0))).init(a(0))
+ }
+
+ // Scalar functions
+ def keyword(a: List[Any]) = {
+ "\u029e" + a(0).asInstanceOf[String]
+ }
+
+ def keyword_Q(a: List[Any]) = {
+ a(0) match {
+ case s: String => s(0) == '\u029e'
+ case _ => false
+ }
+ }
+
+
+ // number functions
+ def _bool_op(a: List[Any], op: (Long, Long) => Boolean) = {
+ op(a(0).asInstanceOf[Long],a(1).asInstanceOf[Long])
+ }
+
+ def _num_op(a: List[Any], op: (Long, Long) => Long) = {
+ op(a(0).asInstanceOf[Long],a(1).asInstanceOf[Long])
+ }
+
+
+ // string functions
+ def read_string(a: List[Any]) = {
+ reader.read_str(a(0).asInstanceOf[String])
+ }
+
+ def slurp(a: List[Any]) = {
+ Source.fromFile(a(0).asInstanceOf[String]).getLines.mkString("\n") + "\n"
+ }
+
+ // Hash Map functions
+ def assoc(a: List[Any]): Any = {
+ a(0).asInstanceOf[MalHashMap] ++ _hash_map(a.drop(1):_*)
+ }
+
+ def dissoc(a: List[Any]): Any = {
+ var kSet = a.drop(1).toSet
+ a(0).asInstanceOf[MalHashMap]
+ .filterKeys{ !kSet.contains(_) }
+ }
+
+ def get(a: List[Any]): Any = {
+ val hm = a(0).asInstanceOf[MalHashMap]
+ val key = a(1).asInstanceOf[String]
+ if (hm != null && hm.value.contains(key)) hm(key) else null
+ }
+
+ def contains_Q(a: List[Any]): Any = {
+ a(0).asInstanceOf[MalHashMap].value
+ .contains(a(1).asInstanceOf[String])
+ }
+
+
+ // sequence functions
+ def concat(a: List[Any]): Any = {
+ _list((for (sq <- a) yield types._toIter(sq)).flatten:_*)
+ }
+
+ def nth(a: List[Any]): Any = {
+ val lst = a(0).asInstanceOf[MalList].value
+ val idx = a(1).asInstanceOf[Long]
+ if (idx < lst.length) {
+ lst(idx.toInt)
+ } else {
+ throw new Exception("nth: index out of range")
+ }
+ }
+
+ def first(a: List[Any]): Any = {
+ val lst = a(0).asInstanceOf[MalList].value
+ if (lst.length > 0) lst(0) else null
+ }
+
+ def rest(a: List[Any]): Any = {
+ a(0) match {
+ case null => true
+ case ml: MalList => _list(ml.drop(1).value:_*)
+ }
+ }
+
+ def empty_Q(a: List[Any]): Any = {
+ a(0) match {
+ case null => true
+ case ml: MalList => ml.value.isEmpty
+ }
+ }
+
+ def count(a: List[Any]): Any = {
+ a(0) match {
+ case null => 0
+ case ml: MalList => ml.value.length.asInstanceOf[Long]
+ }
+ }
+
+ def conj(a: List[Any]): Any = {
+ a(0) match {
+ case mv: MalVector => {
+ _vector(mv.value ++ a.slice(1,a.length):_*)
+ }
+ case ml: MalList => {
+ _list(a.slice(1,a.length).reverse ++ ml.value:_*)
+ }
+ }
+ }
+
+ def apply(a: List[Any]): Any = {
+ a match {
+ case f :: rest => {
+ var args1 = rest.slice(0,rest.length-1)
+ var args = args1 ++ rest(rest.length-1).asInstanceOf[MalList].value
+ types._apply(f, args)
+ }
+ case _ => throw new Exception("invalid apply call")
+ }
+ }
+
+ def do_map(a: List[Any]): Any = {
+ a match {
+ case f :: seq :: Nil => {
+ seq.asInstanceOf[MalList].map(x => types._apply(f,List(x)))
+ }
+ case _ => throw new Exception("invalid map call")
+ }
+ }
+
+
+ // meta functions
+ def with_meta(a: List[Any]): Any = {
+ val meta: Any = a(1)
+ a(0) match {
+ case ml: MalList => {
+ val new_ml = ml.clone()
+ new_ml.meta = meta
+ new_ml
+ }
+ case hm: MalHashMap => {
+ val new_hm = hm.clone()
+ new_hm.meta = meta
+ new_hm
+ }
+ case fn: Func => {
+ val new_fn = fn.clone()
+ new_fn.meta = meta
+ new_fn
+ }
+ case fn: MalFunction => {
+ val new_fn = fn.clone()
+ new_fn.meta = meta
+ new_fn
+ }
+ case _ => throw new Exception("no meta support for " + a(0).getClass)
+ }
+ }
+
+ def meta(a: List[Any]): Any = {
+ a(0) match {
+ case ml: MalList => ml.meta
+ case hm: MalHashMap => hm.meta
+ case fn: Func => fn.meta
+ case fn: MalFunction => fn.meta
+ case _ => throw new Exception("no meta support for " + a(0).getClass)
+ }
+ }
+
+
+ // atom functions
+ def reset_BANG(a: List[Any]): Any = {
+ a(0).asInstanceOf[types.Atom].value = a(1)
+ a(1)
+ }
+
+ def swap_BANG(a: List[Any]): Any = {
+ a match {
+ case a0 :: f :: rest => {
+ val atm = a0.asInstanceOf[types.Atom]
+ val args = atm.value +: rest
+ atm.value = types._apply(f, args)
+ atm.value
+ }
+ case _ => throw new Exception("invalid swap! call")
+ }
+ }
+
+
+ val ns: Map[String, (List[Any]) => Any] = Map(
+ "=" -> ((a: List[Any]) => types._equal_Q(a(0), a(1))),
+ "throw" -> mal_throw _,
+ "nil?" -> ((a: List[Any]) => a(0) == null),
+ "true?" -> ((a: List[Any]) => a(0) == true),
+ "false?" -> ((a: List[Any]) => a(0) == false),
+ "symbol" -> ((a: List[Any]) => Symbol(a(0).asInstanceOf[String])),
+ "symbol?" -> ((a: List[Any]) => a(0).isInstanceOf[Symbol]),
+ "keyword" -> keyword _,
+ "keyword?" -> keyword_Q _,
+
+ "pr-str" -> ((a: List[Any]) => _pr_list(a, true, " ")),
+ "str" -> ((a: List[Any]) => _pr_list(a, false, "")),
+ "prn" -> ((a: List[Any]) => { println(_pr_list(a, true, " ")); null}),
+ "println" -> ((a: List[Any]) => { println(_pr_list(a, false, " ")); null}),
+ "readline" -> ((a: List[Any]) => readLine(a(0).asInstanceOf[String])),
+ "read-string" -> read_string _,
+ "slurp" -> slurp _,
+
+ "<" -> ((a: List[Any]) => _bool_op(a, _ < _)),
+ "<=" -> ((a: List[Any]) => _bool_op(a, _ <= _)),
+ ">" -> ((a: List[Any]) => _bool_op(a, _ > _)),
+ ">=" -> ((a: List[Any]) => _bool_op(a, _ >= _)),
+ "+" -> ((a: List[Any]) => _num_op(a, _ + _)),
+ "-" -> ((a: List[Any]) => _num_op(a, _ - _)),
+ "*" -> ((a: List[Any]) => _num_op(a, _ * _)),
+ "/" -> ((a: List[Any]) => _num_op(a, _ / _)),
+ "time-ms" -> ((a: List[Any]) => System.currentTimeMillis),
+
+ "list" -> ((a: List[Any]) => _list(a:_*)),
+ "list?" -> ((a: List[Any]) => _list_Q(a(0))),
+ "vector" -> ((a: List[Any]) => _vector(a:_*)),
+ "vector?" -> ((a: List[Any]) => _vector_Q(a(0))),
+ "hash-map" -> ((a: List[Any]) => _hash_map(a:_*)),
+ "map?" -> ((a: List[Any]) => _hash_map_Q(a(0))),
+ "assoc" -> assoc _,
+ "dissoc" -> dissoc _,
+ "get" -> get _,
+ "contains?" -> contains_Q _,
+ "keys" -> ((a: List[Any]) => a(0).asInstanceOf[MalHashMap].keys),
+ "vals" -> ((a: List[Any]) => a(0).asInstanceOf[MalHashMap].vals),
+
+ "sequential?" -> ((a: List[Any]) => types._sequential_Q(a(0))),
+ "cons" -> ((a: List[Any]) => a(0) +: a(1).asInstanceOf[MalList]),
+ "concat" -> concat _,
+ "nth" -> nth _,
+ "first" -> first _,
+ "rest" -> rest _,
+ "empty?" -> empty_Q _,
+ "count" -> count _,
+ "conj" -> conj _,
+ "apply" -> apply _,
+ "map" -> do_map _,
+
+ "with-meta" -> with_meta _,
+ "meta" -> meta _,
+ "atom" -> ((a: List[Any]) => new types.Atom(a(0))),
+ "atom?" -> ((a: List[Any]) => a(0).isInstanceOf[types.Atom]),
+ "deref" -> ((a: List[Any]) => a(0).asInstanceOf[types.Atom].value),
+ "reset!" -> reset_BANG _,
+ "swap!" -> swap_BANG _
+ )
+}
+
+// vim:ts=2:sw=2
diff --git a/scala/env.scala b/scala/env.scala
new file mode 100644
index 0000000..33be545
--- /dev/null
+++ b/scala/env.scala
@@ -0,0 +1,42 @@
+import types._list
+
+import scala.collection.mutable
+
+object env {
+ class Env(outer: Env = null,
+ binds: Iterator[Any] = null,
+ exprs: Iterator[Any] = null) {
+ val data: mutable.Map[Symbol, Any] = mutable.Map()
+ if (binds != null && exprs != null) {
+ binds.foreach(b => {
+ val k = b.asInstanceOf[Symbol]
+ if (k == '&) {
+ data(binds.next().asInstanceOf[Symbol]) = _list(exprs.toSeq:_*)
+ } else {
+ data(k) = exprs.next()
+ }
+ })
+ }
+
+ def find(key: Symbol): Env = {
+ if (data.contains(key)) {
+ this
+ } else if (outer != null) {
+ outer.find(key)
+ } else {
+ null
+ }
+ }
+ def set(key: Symbol, value: Any): Any = {
+ data(key) = value
+ value
+ }
+ def get(key: Symbol): Any = {
+ val env = find(key)
+ if (env == null) throw new Exception("'" + key.name + "' not found")
+ env.data(key)
+ }
+ }
+}
+
+// vim:ts=2:sw=2
diff --git a/scala/printer.scala b/scala/printer.scala
new file mode 100644
index 0000000..0a0e0b7
--- /dev/null
+++ b/scala/printer.scala
@@ -0,0 +1,43 @@
+import types.{MalList, MalVector, MalHashMap, MalFunction}
+
+
+object printer {
+ def _pr_str(obj: Any, print_readably: Boolean = true): String = {
+ val _r = print_readably
+ return obj match {
+ case v: MalVector => v.toString(_r)
+ case l: MalList => l.toString(_r)
+ case hm: MalHashMap => hm.toString(_r)
+ case s: String => {
+ if (s.length > 0 && s(0) == '\u029e') {
+ ":" + s.substring(1,s.length)
+ } else if (_r) {
+ //println("here1: " + s)
+ "\"" + s.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n") + "\""
+ } else {
+ s
+ }
+ }
+ case Symbol(s) => s
+ case a: types.Atom => "(atom " + a.value + ")"
+ case null => "nil"
+ case _ => {
+ if (obj.isInstanceOf[MalFunction]) {
+ val f = obj.asInstanceOf[MalFunction]
+ "<function (fn* " + _pr_str(f.params) + " " + _pr_str(f.ast) + ")>"
+ } else {
+ obj.toString
+ }
+ }
+ }
+ }
+
+ def _pr_list(lst: List[Any], print_readably: Boolean = true,
+ sep: String = " "): String = {
+ lst.map{_pr_str(_, print_readably)}.mkString(sep)
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/reader.scala b/scala/reader.scala
new file mode 100644
index 0000000..de45923
--- /dev/null
+++ b/scala/reader.scala
@@ -0,0 +1,90 @@
+import scala.util.matching.Regex
+
+import types.{MalList, _list, MalVector, _vector, MalHashMap, _hash_map}
+
+object reader {
+
+ class Reader (tokens: Array[String]) {
+ var data = tokens
+ var position: Int = 0
+ def peek(): String = {
+ if (position >= data.length) return(null)
+ data(position)
+ }
+ def next(): String = {
+ if (position >= data.length) return(null)
+ position = position + 1
+ data(position-1)
+ }
+ }
+
+ def tokenize(str: String): Array[String] = {
+ val re = """[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)""".r
+ re.findAllMatchIn(str).map{ _.group(1) }
+ .filter{ s => s != "" && s(0) != ';' }
+ .toArray
+ }
+
+ def parse_str(s: String): String = {
+ s.replace("\\\"", "\"").replace("\\n", "\n")
+ }
+
+ def read_atom(rdr: Reader): Any = {
+ val token = rdr.next()
+ val re_int = """^(-?[0-9]+)$""".r
+ val re_flt = """^(-?[0-9][0-9.]*)$""".r
+ val re_str = """^"(.*)"$""".r
+ val re_key = """^:(.*)$""".r
+ return token match {
+ case re_int(i) => i.toLong // integer
+ case re_flt(f) => f.toDouble // float
+ case re_str(s) => parse_str(s) // string
+ case re_key(k) => "\u029e" + k // keyword
+ case "nil" => null
+ case "true" => true
+ case "false" => false
+ case _ => Symbol(token) // symbol
+ }
+ }
+
+ def read_list(rdr: Reader,
+ start: String = "(", end: String = ")"): MalList = {
+ var ast: MalList = _list()
+ var token = rdr.next()
+ if (token != start) throw new Exception("expected '" + start + "', got EOF")
+ while ({token = rdr.peek(); token != end}) {
+ if (token == null) throw new Exception("expected '" + end + "', got EOF")
+ ast = ast :+ read_form(rdr)
+ }
+ rdr.next()
+ ast
+ }
+
+ def read_form(rdr: Reader): Any = {
+ return rdr.peek() match {
+ case "'" => { rdr.next; _list(Symbol("quote"), read_form(rdr)) }
+ case "`" => { rdr.next; _list(Symbol("quasiquote"), read_form(rdr)) }
+ case "~" => { rdr.next; _list(Symbol("unquote"), read_form(rdr)) }
+ case "~@" => { rdr.next; _list(Symbol("splice-unquote"), read_form(rdr)) }
+ case "^" => { rdr.next; val meta = read_form(rdr);
+ _list(Symbol("with-meta"), read_form(rdr), meta) }
+ case "@" => { rdr.next; _list(Symbol("deref"), read_form(rdr)) }
+
+ case "(" => read_list(rdr)
+ case ")" => throw new Exception("unexpected ')')")
+ case "[" => _vector(read_list(rdr, "[", "]").value:_*)
+ case "]" => throw new Exception("unexpected ']')")
+ case "{" => _hash_map(read_list(rdr, "{", "}").value:_*)
+ case "}" => throw new Exception("unexpected '}')")
+ case _ => read_atom(rdr)
+ }
+ }
+
+ def read_str(str: String): Any = {
+ val tokens = tokenize(str)
+ if (tokens.length == 0) return null
+ return read_form(new Reader(tokens))
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step0_repl.scala b/scala/step0_repl.scala
new file mode 100644
index 0000000..5ec94fe
--- /dev/null
+++ b/scala/step0_repl.scala
@@ -0,0 +1,33 @@
+object step0_repl {
+ def READ(str: String): String = {
+ str
+ }
+
+ def EVAL(str: String, env: String): String = {
+ str
+ }
+
+ def PRINT(str: String): String = {
+ str
+ }
+
+ def REP(str: String): String = {
+ PRINT(EVAL(READ(str), ""))
+ }
+
+ def main(args: Array[String]) {
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Exception => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step1_read_print.scala b/scala/step1_read_print.scala
new file mode 100644
index 0000000..7485eb0
--- /dev/null
+++ b/scala/step1_read_print.scala
@@ -0,0 +1,39 @@
+import reader.tokenize
+
+object step1_read_print {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def EVAL(ast: Any, env: String): Any = {
+ ast
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val REP = (str: String) => {
+ PRINT(EVAL(READ(str), ""))
+ }
+
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Exception => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step2_eval.scala b/scala/step2_eval.scala
new file mode 100644
index 0000000..d31d339
--- /dev/null
+++ b/scala/step2_eval.scala
@@ -0,0 +1,73 @@
+import types.{MalList, _list_Q, MalVector, MalHashMap, MalFunction}
+
+object step2_eval {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def eval_ast(ast: Any, env: Map[Symbol,Any]): Any = {
+ ast match {
+ case s : Symbol => env(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(ast: Any, env: Map[Symbol,Any]): Any = {
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ var fn: List[Any] => Any = null
+ try {
+ fn = f.asInstanceOf[List[Any] => Any]
+ } catch {
+ case _: Throwable =>
+ throw new Exception("attempt to call non-function")
+ }
+ return fn(el)
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Map[Symbol,Any] = Map(
+ '+ -> ((a: List[Any]) => a(0).asInstanceOf[Long] + a(1).asInstanceOf[Long]),
+ '- -> ((a: List[Any]) => a(0).asInstanceOf[Long] - a(1).asInstanceOf[Long]),
+ '* -> ((a: List[Any]) => a(0).asInstanceOf[Long] * a(1).asInstanceOf[Long]),
+ '/ -> ((a: List[Any]) => a(0).asInstanceOf[Long] / a(1).asInstanceOf[Long]))
+ val REP = (str: String) => {
+ PRINT(EVAL(READ(str), repl_env))
+ }
+
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Exception => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step3_env.scala b/scala/step3_env.scala
new file mode 100644
index 0000000..37572fe
--- /dev/null
+++ b/scala/step3_env.scala
@@ -0,0 +1,89 @@
+import types.{MalList, _list_Q, MalVector, MalHashMap, MalFunction}
+import env.Env
+
+object step3_env {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(ast: Any, env: Env): Any = {
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ return EVAL(a2, let_env)
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ var fn: List[Any] => Any = null
+ try {
+ fn = f.asInstanceOf[(List[Any]) => Any]
+ } catch {
+ case _: Throwable =>
+ throw new Exception("attempt to call non-function")
+ }
+ return fn(el)
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ repl_env.set('+, (a: List[Any]) => a(0).asInstanceOf[Long] + a(1).asInstanceOf[Long])
+ repl_env.set('-, (a: List[Any]) => a(0).asInstanceOf[Long] - a(1).asInstanceOf[Long])
+ repl_env.set('*, (a: List[Any]) => a(0).asInstanceOf[Long] * a(1).asInstanceOf[Long])
+ repl_env.set('/, (a: List[Any]) => a(0).asInstanceOf[Long] / a(1).asInstanceOf[Long])
+ val REP = (str: String) => {
+ PRINT(EVAL(READ(str), repl_env))
+ }
+
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step4_if_fn_do.scala b/scala/step4_if_fn_do.scala
new file mode 100644
index 0000000..b6c2dc1
--- /dev/null
+++ b/scala/step4_if_fn_do.scala
@@ -0,0 +1,110 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object step4_if_fn_do {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(ast: Any, env: Env): Any = {
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ return EVAL(a2, let_env)
+ }
+ case Symbol("do") :: rest => {
+ val el = eval_ast(_list(rest:_*), env)
+ return el.asInstanceOf[MalList].value.last
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ return EVAL(rest(0), env)
+ } else {
+ return EVAL(a2, env)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new Func((args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ })
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ var fn: Func = null
+ try {
+ fn = f.asInstanceOf[Func]
+ } catch {
+ case _: Throwable =>
+ throw new Exception("attempt to call non-function")
+ }
+ return fn(el)
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))")
+
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step5_tco.scala b/scala/step5_tco.scala
new file mode 100644
index 0000000..c4511e5
--- /dev/null
+++ b/scala/step5_tco.scala
@@ -0,0 +1,121 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object step5_tco {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(orig_ast: Any, orig_env: Env): Any = {
+ var ast = orig_ast; var env = orig_env;
+ while (true) {
+
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ env = let_env
+ ast = a2 // continue loop (TCO)
+ }
+ case Symbol("do") :: rest => {
+ eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
+ ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ ast = rest(0) // continue loop (TCO)
+ } else {
+ ast = a2 // continue loop (TCO)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new MalFunction(a2, env, a1.asInstanceOf[MalList],
+ (args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ }
+ )
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ f match {
+ case fn: MalFunction => {
+ env = fn.gen_env(el)
+ ast = fn.ast // continue loop (TCO)
+ }
+ case fn: Func => {
+ return fn(el)
+ }
+ case _ => {
+ throw new Exception("attempt to call non-function: " + f)
+ }
+ }
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))")
+
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step6_file.scala b/scala/step6_file.scala
new file mode 100644
index 0000000..0dad7cd
--- /dev/null
+++ b/scala/step6_file.scala
@@ -0,0 +1,130 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object step6_file {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(orig_ast: Any, orig_env: Env): Any = {
+ var ast = orig_ast; var env = orig_env;
+ while (true) {
+
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ env = let_env
+ ast = a2 // continue loop (TCO)
+ }
+ case Symbol("do") :: rest => {
+ eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
+ ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ ast = rest(0) // continue loop (TCO)
+ } else {
+ ast = a2 // continue loop (TCO)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new MalFunction(a2, env, a1.asInstanceOf[MalList],
+ (args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ }
+ )
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ f match {
+ case fn: MalFunction => {
+ env = fn.gen_env(el)
+ ast = fn.ast // continue loop (TCO)
+ }
+ case fn: Func => {
+ return fn(el)
+ }
+ case _ => {
+ throw new Exception("attempt to call non-function: " + f)
+ }
+ }
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+ repl_env.set(Symbol("eval"), new Func((a: List[Any]) => EVAL(a(0), repl_env)))
+ repl_env.set(Symbol("*ARGV*"), _list(args.slice(1,args.length):_*))
+
+ // 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 (args.length > 0) {
+ REP("(load-file \"" + args(0) + "\")")
+ System.exit(0)
+ }
+
+ // repl loop
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step7_quote.scala b/scala/step7_quote.scala
new file mode 100644
index 0000000..3f0a4d8
--- /dev/null
+++ b/scala/step7_quote.scala
@@ -0,0 +1,163 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object step7_quote {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def is_pair(x: Any): Boolean = {
+ types._sequential_Q(x) && x.asInstanceOf[MalList].value.length > 0
+ }
+
+ def quasiquote(ast: Any): Any = {
+ if (!is_pair(ast)) {
+ return _list(Symbol("quote"), ast)
+ } else {
+ val a0 = ast.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a0) &&
+ a0.asInstanceOf[Symbol].name == "unquote") {
+ return ast.asInstanceOf[MalList](1)
+ } else if (is_pair(a0)) {
+ val a00 = a0.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a00) &&
+ a00.asInstanceOf[Symbol].name == "splice-unquote") {
+ return _list(Symbol("concat"),
+ a0.asInstanceOf[MalList](1),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+ return _list(Symbol("cons"),
+ quasiquote(a0),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(orig_ast: Any, orig_env: Env): Any = {
+ var ast = orig_ast; var env = orig_env;
+ while (true) {
+
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ env = let_env
+ ast = a2 // continue loop (TCO)
+ }
+ case Symbol("quote") :: a1 :: Nil => {
+ return a1
+ }
+ case Symbol("quasiquote") :: a1 :: Nil => {
+ ast = quasiquote(a1) // continue loop (TCO)
+ }
+ case Symbol("do") :: rest => {
+ eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
+ ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ ast = rest(0) // continue loop (TCO)
+ } else {
+ ast = a2 // continue loop (TCO)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new MalFunction(a2, env, a1.asInstanceOf[MalList],
+ (args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ }
+ )
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ f match {
+ case fn: MalFunction => {
+ env = fn.gen_env(el)
+ ast = fn.ast // continue loop (TCO)
+ }
+ case fn: Func => {
+ return fn(el)
+ }
+ case _ => {
+ throw new Exception("attempt to call non-function: " + f)
+ }
+ }
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+ repl_env.set(Symbol("eval"), new Func((a: List[Any]) => EVAL(a(0), repl_env)))
+ repl_env.set(Symbol("*ARGV*"), _list(args.slice(1,args.length):_*))
+
+ // 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 (args.length > 0) {
+ REP("(load-file \"" + args(0) + "\")")
+ System.exit(0)
+ }
+
+ // repl loop
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step8_macros.scala b/scala/step8_macros.scala
new file mode 100644
index 0000000..e1a2222
--- /dev/null
+++ b/scala/step8_macros.scala
@@ -0,0 +1,207 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object step8_macros {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def is_pair(x: Any): Boolean = {
+ types._sequential_Q(x) && x.asInstanceOf[MalList].value.length > 0
+ }
+
+ def quasiquote(ast: Any): Any = {
+ if (!is_pair(ast)) {
+ return _list(Symbol("quote"), ast)
+ } else {
+ val a0 = ast.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a0) &&
+ a0.asInstanceOf[Symbol].name == "unquote") {
+ return ast.asInstanceOf[MalList](1)
+ } else if (is_pair(a0)) {
+ val a00 = a0.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a00) &&
+ a00.asInstanceOf[Symbol].name == "splice-unquote") {
+ return _list(Symbol("concat"),
+ a0.asInstanceOf[MalList](1),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+ return _list(Symbol("cons"),
+ quasiquote(a0),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+
+ def is_macro_call(ast: Any, env: Env): Boolean = {
+ ast match {
+ case ml: MalList => {
+ if (types._symbol_Q(ml(0)) &&
+ env.find(ml(0).asInstanceOf[Symbol]) != null) {
+ env.get(ml(0).asInstanceOf[Symbol]) match {
+ case f: MalFunction => return f.ismacro
+ case _ => return false
+ }
+ }
+ return false
+ }
+ case _ => return false
+ }
+ }
+
+ def macroexpand(orig_ast: Any, env: Env): Any = {
+ var ast = orig_ast;
+ while (is_macro_call(ast, env)) {
+ ast.asInstanceOf[MalList].value match {
+ case f :: args => {
+ val mac = env.get(f.asInstanceOf[Symbol])
+ ast = mac.asInstanceOf[MalFunction](args)
+ }
+ case _ => throw new Exception("macroexpand: invalid call")
+ }
+ }
+ ast
+ }
+
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(orig_ast: Any, orig_env: Env): Any = {
+ var ast = orig_ast; var env = orig_env;
+ while (true) {
+
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast = macroexpand(ast, env)
+ if (!_list_Q(ast)) return ast
+
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ env = let_env
+ ast = a2 // continue loop (TCO)
+ }
+ case Symbol("quote") :: a1 :: Nil => {
+ return a1
+ }
+ case Symbol("quasiquote") :: a1 :: Nil => {
+ ast = quasiquote(a1) // continue loop (TCO)
+ }
+ case Symbol("defmacro!") :: a1 :: a2 :: Nil => {
+ val f = EVAL(a2, env)
+ f.asInstanceOf[MalFunction].ismacro = true
+ return env.set(a1.asInstanceOf[Symbol], f)
+ }
+ case Symbol("macroexpand") :: a1 :: Nil => {
+ return macroexpand(a1, env)
+ }
+ case Symbol("do") :: rest => {
+ eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
+ ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ ast = rest(0) // continue loop (TCO)
+ } else {
+ ast = a2 // continue loop (TCO)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new MalFunction(a2, env, a1.asInstanceOf[MalList],
+ (args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ }
+ )
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ f match {
+ case fn: MalFunction => {
+ env = fn.gen_env(el)
+ ast = fn.ast // continue loop (TCO)
+ }
+ case fn: Func => {
+ return fn(el)
+ }
+ case _ => {
+ throw new Exception("attempt to call non-function: " + f)
+ }
+ }
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+ repl_env.set(Symbol("eval"), new Func((a: List[Any]) => EVAL(a(0), repl_env)))
+ repl_env.set(Symbol("*ARGV*"), _list(args.slice(1,args.length):_*))
+
+ // 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 (args.length > 0) {
+ REP("(load-file \"" + args(0) + "\")")
+ System.exit(0)
+ }
+
+ // repl loop
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step9_try.scala b/scala/step9_try.scala
new file mode 100644
index 0000000..02c19be
--- /dev/null
+++ b/scala/step9_try.scala
@@ -0,0 +1,227 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object step9_try {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def is_pair(x: Any): Boolean = {
+ types._sequential_Q(x) && x.asInstanceOf[MalList].value.length > 0
+ }
+
+ def quasiquote(ast: Any): Any = {
+ if (!is_pair(ast)) {
+ return _list(Symbol("quote"), ast)
+ } else {
+ val a0 = ast.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a0) &&
+ a0.asInstanceOf[Symbol].name == "unquote") {
+ return ast.asInstanceOf[MalList](1)
+ } else if (is_pair(a0)) {
+ val a00 = a0.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a00) &&
+ a00.asInstanceOf[Symbol].name == "splice-unquote") {
+ return _list(Symbol("concat"),
+ a0.asInstanceOf[MalList](1),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+ return _list(Symbol("cons"),
+ quasiquote(a0),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+
+ def is_macro_call(ast: Any, env: Env): Boolean = {
+ ast match {
+ case ml: MalList => {
+ if (types._symbol_Q(ml(0)) &&
+ env.find(ml(0).asInstanceOf[Symbol]) != null) {
+ env.get(ml(0).asInstanceOf[Symbol]) match {
+ case f: MalFunction => return f.ismacro
+ case _ => return false
+ }
+ }
+ return false
+ }
+ case _ => return false
+ }
+ }
+
+ def macroexpand(orig_ast: Any, env: Env): Any = {
+ var ast = orig_ast;
+ while (is_macro_call(ast, env)) {
+ ast.asInstanceOf[MalList].value match {
+ case f :: args => {
+ val mac = env.get(f.asInstanceOf[Symbol])
+ ast = mac.asInstanceOf[MalFunction](args)
+ }
+ case _ => throw new Exception("macroexpand: invalid call")
+ }
+ }
+ ast
+ }
+
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(orig_ast: Any, orig_env: Env): Any = {
+ var ast = orig_ast; var env = orig_env;
+ while (true) {
+
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast = macroexpand(ast, env)
+ if (!_list_Q(ast)) return ast
+
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ env = let_env
+ ast = a2 // continue loop (TCO)
+ }
+ case Symbol("quote") :: a1 :: Nil => {
+ return a1
+ }
+ case Symbol("quasiquote") :: a1 :: Nil => {
+ ast = quasiquote(a1) // continue loop (TCO)
+ }
+ case Symbol("defmacro!") :: a1 :: a2 :: Nil => {
+ val f = EVAL(a2, env)
+ f.asInstanceOf[MalFunction].ismacro = true
+ return env.set(a1.asInstanceOf[Symbol], f)
+ }
+ case Symbol("macroexpand") :: a1 :: Nil => {
+ return macroexpand(a1, env)
+ }
+ case Symbol("try*") :: a1 :: rest => {
+ try {
+ return EVAL(a1, env)
+ } catch {
+ case t: Throwable => {
+ rest(0).asInstanceOf[MalList].value match {
+ case List(Symbol("catch*"), a21, a22) => {
+ val exc: Any = t match {
+ case mex: types.MalException => mex.value
+ case _ => t.getMessage
+ }
+ return EVAL(a22, new Env(env,
+ List(a21).iterator,
+ List(exc).iterator))
+ }
+ }
+ throw t
+ }
+ }
+ }
+ case Symbol("do") :: rest => {
+ eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
+ ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ ast = rest(0) // continue loop (TCO)
+ } else {
+ ast = a2 // continue loop (TCO)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new MalFunction(a2, env, a1.asInstanceOf[MalList],
+ (args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ }
+ )
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ f match {
+ case fn: MalFunction => {
+ env = fn.gen_env(el)
+ ast = fn.ast // continue loop (TCO)
+ }
+ case fn: Func => {
+ return fn(el)
+ }
+ case _ => {
+ throw new Exception("attempt to call non-function: " + f)
+ }
+ }
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+ repl_env.set(Symbol("eval"), new Func((a: List[Any]) => EVAL(a(0), repl_env)))
+ repl_env.set(Symbol("*ARGV*"), _list(args.slice(1,args.length):_*))
+
+ // 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 (args.length > 0) {
+ REP("(load-file \"" + args(0) + "\")")
+ System.exit(0)
+ }
+
+ // repl loop
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/stepA_mal.scala b/scala/stepA_mal.scala
new file mode 100644
index 0000000..5230cb1
--- /dev/null
+++ b/scala/stepA_mal.scala
@@ -0,0 +1,229 @@
+import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
+ Func, MalFunction}
+import env.Env
+
+object stepA_mal {
+ // read
+ def READ(str: String): Any = {
+ reader.read_str(str)
+ }
+
+ // eval
+ def is_pair(x: Any): Boolean = {
+ types._sequential_Q(x) && x.asInstanceOf[MalList].value.length > 0
+ }
+
+ def quasiquote(ast: Any): Any = {
+ if (!is_pair(ast)) {
+ return _list(Symbol("quote"), ast)
+ } else {
+ val a0 = ast.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a0) &&
+ a0.asInstanceOf[Symbol].name == "unquote") {
+ return ast.asInstanceOf[MalList](1)
+ } else if (is_pair(a0)) {
+ val a00 = a0.asInstanceOf[MalList](0)
+ if (types._symbol_Q(a00) &&
+ a00.asInstanceOf[Symbol].name == "splice-unquote") {
+ return _list(Symbol("concat"),
+ a0.asInstanceOf[MalList](1),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+ return _list(Symbol("cons"),
+ quasiquote(a0),
+ quasiquote(ast.asInstanceOf[MalList].drop(1)))
+ }
+ }
+
+ def is_macro_call(ast: Any, env: Env): Boolean = {
+ ast match {
+ case ml: MalList => {
+ if (types._symbol_Q(ml(0)) &&
+ env.find(ml(0).asInstanceOf[Symbol]) != null) {
+ env.get(ml(0).asInstanceOf[Symbol]) match {
+ case f: MalFunction => return f.ismacro
+ case _ => return false
+ }
+ }
+ return false
+ }
+ case _ => return false
+ }
+ }
+
+ def macroexpand(orig_ast: Any, env: Env): Any = {
+ var ast = orig_ast;
+ while (is_macro_call(ast, env)) {
+ ast.asInstanceOf[MalList].value match {
+ case f :: args => {
+ val mac = env.get(f.asInstanceOf[Symbol])
+ ast = mac.asInstanceOf[MalFunction](args)
+ }
+ case _ => throw new Exception("macroexpand: invalid call")
+ }
+ }
+ ast
+ }
+
+ def eval_ast(ast: Any, env: Env): Any = {
+ ast match {
+ case s : Symbol => env.get(s)
+ case v: MalVector => v.map(EVAL(_, env))
+ case l: MalList => l.map(EVAL(_, env))
+ case m: MalHashMap => {
+ m.map{case (k: String,v: Any) => (k, EVAL(v, env))}
+ }
+ case _ => ast
+ }
+ }
+
+ def EVAL(orig_ast: Any, orig_env: Env): Any = {
+ var ast = orig_ast; var env = orig_env;
+ while (true) {
+
+ //println("EVAL: " + printer._pr_str(ast,true))
+ if (!_list_Q(ast))
+ return eval_ast(ast, env)
+
+ // apply list
+ ast = macroexpand(ast, env)
+ if (!_list_Q(ast)) return ast
+
+ ast.asInstanceOf[MalList].value match {
+ case Symbol("def!") :: a1 :: a2 :: Nil => {
+ return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+ }
+ case Symbol("let*") :: a1 :: a2 :: Nil => {
+ val let_env = new Env(env)
+ for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
+ let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+ }
+ env = let_env
+ ast = a2 // continue loop (TCO)
+ }
+ case Symbol("quote") :: a1 :: Nil => {
+ return a1
+ }
+ case Symbol("quasiquote") :: a1 :: Nil => {
+ ast = quasiquote(a1) // continue loop (TCO)
+ }
+ case Symbol("defmacro!") :: a1 :: a2 :: Nil => {
+ val f = EVAL(a2, env)
+ f.asInstanceOf[MalFunction].ismacro = true
+ return env.set(a1.asInstanceOf[Symbol], f)
+ }
+ case Symbol("macroexpand") :: a1 :: Nil => {
+ return macroexpand(a1, env)
+ }
+ case Symbol("try*") :: a1 :: rest => {
+ try {
+ return EVAL(a1, env)
+ } catch {
+ case t: Throwable => {
+ rest(0).asInstanceOf[MalList].value match {
+ case List(Symbol("catch*"), a21, a22) => {
+ val exc: Any = t match {
+ case mex: types.MalException => mex.value
+ case _ => t.getMessage
+ }
+ return EVAL(a22, new Env(env,
+ List(a21).iterator,
+ List(exc).iterator))
+ }
+ }
+ throw t
+ }
+ }
+ }
+ case Symbol("do") :: rest => {
+ eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
+ ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
+ }
+ case Symbol("if") :: a1 :: a2 :: rest => {
+ val cond = EVAL(a1, env)
+ if (cond == null || cond == false) {
+ if (rest.length == 0) return null
+ ast = rest(0) // continue loop (TCO)
+ } else {
+ ast = a2 // continue loop (TCO)
+ }
+ }
+ case Symbol("fn*") :: a1 :: a2 :: Nil => {
+ return new MalFunction(a2, env, a1.asInstanceOf[MalList],
+ (args: List[Any]) => {
+ EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+ }
+ )
+ }
+ case _ => {
+ // function call
+ eval_ast(ast, env).asInstanceOf[MalList].value match {
+ case f :: el => {
+ f match {
+ case fn: MalFunction => {
+ env = fn.gen_env(el)
+ ast = fn.ast // continue loop (TCO)
+ }
+ case fn: Func => {
+ return fn(el)
+ }
+ case _ => {
+ throw new Exception("attempt to call non-function: " + f)
+ }
+ }
+ }
+ case _ => throw new Exception("invalid apply")
+ }
+ }
+ }
+ }
+ }
+
+ // print
+ def PRINT(exp: Any): String = {
+ printer._pr_str(exp, true)
+ }
+
+ // repl
+ def main(args: Array[String]) = {
+ val repl_env: Env = new Env()
+ val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+ // core.scala: defined using scala
+ core.ns.map{case (k: String,v: Any) => {
+ repl_env.set(Symbol(k), new Func(v))
+ }}
+ repl_env.set(Symbol("eval"), new Func((a: List[Any]) => EVAL(a(0), repl_env)))
+ repl_env.set(Symbol("*ARGV*"), _list(args.slice(1,args.length):_*))
+
+ // core.mal: defined using the language itself
+ REP("(def! *host-language* \"scala\")")
+ 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 (args.length > 0) {
+ REP("(load-file \"" + args(0) + "\")")
+ System.exit(0)
+ }
+
+ // repl loop
+ REP("(println (str \"Mal [\" *host-language* \"]\"))")
+ var line:String = null
+ while ({line = readLine("user> "); line != null}) {
+ try {
+ println(REP(line))
+ } catch {
+ case e : Throwable => {
+ println("Error: " + e.getMessage)
+ println(" " + e.getStackTrace.mkString("\n "))
+ }
+ }
+ }
+ }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/types.scala b/scala/types.scala
new file mode 100644
index 0000000..91af068
--- /dev/null
+++ b/scala/types.scala
@@ -0,0 +1,202 @@
+import scala.collection._
+import scala.collection.generic._
+
+import env.Env
+import printer._pr_str
+
+object types {
+ class MalException(msg: String) extends Throwable(msg) {
+ var value: Any = null
+ def init(obj: Any) = { value = obj; this }
+ }
+
+ def _toIter(obj: Any): Iterator[Any] = {
+ obj match {
+ case v: MalVector => v.value.iterator
+ case l: MalList => l.value.iterator
+ case null => Iterator.empty
+ case _ => throw new Exception("cannot convert " +
+ obj.getClass + " to iterator")
+ }
+ }
+
+ def _equal_Q(a: Any, b: Any): Any = {
+ (a, b) match {
+ case (a: MalList, b: MalList) => a.value == b.value
+ case (a: MalHashMap, b: MalHashMap) => a.value == b.value
+ case _ => a == b
+ }
+ }
+
+ def _sequential_Q(a: Any): Boolean = {
+ a match {
+ case l: MalList => true
+ case _ => false
+ }
+ }
+
+ def _symbol_Q(a: Any) = { a.isInstanceOf[Symbol] }
+
+
+ // Lists
+ class MalList(seq: Any*) {
+ var value: List[Any] = seq.toList
+ var meta: Any = null
+
+ override def clone(): MalList = {
+ val new_ml = new MalList()
+ new_ml.value = value
+ new_ml.meta = meta
+ new_ml
+ }
+
+ def apply(idx: Int): Any = value(idx)
+ def map(f: Any => Any) = new MalList(value.map(f):_*)
+ def drop(cnt: Int) = new MalList(value.drop(cnt):_*)
+ def :+(that: Any) = new MalList((value :+ that):_*)
+ def +:(that: Any) = new MalList((that +: value):_*)
+
+ override def toString() = {
+ "(" + value.map(_pr_str(_, true)).mkString(" ") + ")"
+ }
+ def toString(print_readably: Boolean) = {
+ "(" + value.map(_pr_str(_, print_readably)).mkString(" ") + ")"
+ }
+ }
+ def _list(seq: Any*) = {
+ new MalList(seq:_*)
+ }
+ def _list_Q(obj: Any) = {
+ obj.isInstanceOf[MalList] && !obj.isInstanceOf[MalVector]
+ }
+
+
+ // Vectors
+ class MalVector(seq: Any*) extends MalList(seq:_*) {
+ override def clone() = {
+ val new_mv = new MalVector()
+ new_mv.value = value
+ new_mv.meta = meta
+ new_mv
+ }
+
+ override def map(f: Any => Any) = new MalVector(value.map(f):_*)
+ override def drop(cnt: Int) = new MalVector(value.drop(cnt):_*)
+
+ override def toString() = {
+ "[" + value.map(_pr_str(_, true)).mkString(" ") + "]"
+ }
+ override def toString(print_readably: Boolean) = {
+ "[" + value.map(_pr_str(_, print_readably)).mkString(" ") + "]"
+ }
+ }
+ def _vector(seq: Any*) = {
+ new MalVector(seq:_*)
+ }
+ def _vector_Q(obj: Any) = {
+ obj.isInstanceOf[MalVector]
+ }
+
+
+ // Hash Maps
+ class MalHashMap(seq: Any*) {
+ var flat_value: List[Any] = seq.toList
+ var value: Map[String,Any] = flat_value.grouped(2).map(
+ (kv: List[Any]) => (kv(0).asInstanceOf[String], kv(1))).toMap
+ var meta: Any = null
+
+ override def clone(): MalHashMap = {
+ val new_hm = new MalHashMap()
+ new_hm.value = value
+ new_hm.flat_value = flat_value
+ new_hm.meta = meta
+ new_hm
+ }
+
+ def keys(): MalList = new MalList(value.keys.toSeq:_*)
+ def vals(): MalList = new MalList(value.values.toSeq:_*)
+
+ def apply(key: String): Any = value(key)
+ def map(f: ((String, Any)) => (String, Any)) = {
+ val res = value.map(f).map{case (k,v) => List(k,v)}
+ new MalHashMap(res.flatten.toSeq:_*)
+ }
+ def filterKeys(f: String => Boolean) = {
+ val res = value.filterKeys(f).map{case (k,v) => List(k,v)}
+ new MalHashMap(res.flatten.toSeq:_*)
+ }
+ def ++(that: MalHashMap) = {
+ new MalHashMap((flat_value ++ that.flat_value):_*)
+ }
+
+ override def toString() = {
+ "{" + flat_value.map(_pr_str(_, true)).mkString(" ") + "}"
+ }
+ def toString(print_readably: Boolean) = {
+ "{" + flat_value.map(_pr_str(_, print_readably)).mkString(" ") + "}"
+ }
+ }
+ def _hash_map(seq: Any*) = {
+ new MalHashMap(seq:_*)
+ }
+ def _hash_map_Q(obj: Any) = {
+ obj.isInstanceOf[MalHashMap]
+ }
+
+
+ // Function types
+
+ class Func(_fn: ((List[Any]) => Any)) {
+ val fn = _fn
+ var meta: Any = null
+
+ override def clone(): Func = {
+ val new_fn = new Func(fn)
+ new_fn.meta = meta
+ new_fn
+ }
+
+ def apply(args: List[Any]): Any = fn(args)
+ }
+
+ class MalFunction(_ast: Any, _env: Env, _params: MalList,
+ fn: ((List[Any]) => Any)) {
+ val ast = _ast
+ val env = _env
+ val params = _params
+ var ismacro = false
+ var meta: Any = null
+
+ override def clone(): MalFunction = {
+ val new_fn = new MalFunction(ast, env, params, fn)
+ new_fn.ismacro = ismacro
+ new_fn.meta = meta
+ new_fn
+ }
+
+ def apply(args: List[Any]): Any = fn(args)
+
+ def gen_env(args: List[Any]): Env = {
+ return new Env(env, params.value.iterator, args.iterator)
+ }
+ }
+
+ def _apply(f: Any, args: List[Any]): Any = {
+ f match {
+ case fn: types.MalFunction => fn(args)
+ case fn: Func => fn(args)
+ case _ => throw new Exception("attempt to call non-function")
+ }
+ }
+
+ def _hash_map(lst: List[Any]): Any = {
+ lst.grouped(2).map(
+ (kv: List[Any]) => (kv(0).asInstanceOf[String], kv(1))).toMap
+ }
+
+ class Atom(_value: Any) {
+ var value = _value
+ }
+}
+
+// vim:ts=2:sw=2
diff --git a/tests/incA.mal b/tests/incA.mal
new file mode 100644
index 0000000..cbbea79
--- /dev/null
+++ b/tests/incA.mal
@@ -0,0 +1,3 @@
+(def! inc4 (fn* (a) (+ 4 a)))
+
+(prn (inc4 5))
diff --git a/tests/incB.mal b/tests/incB.mal
index 1c68810..519bdf4 100644
--- a/tests/incB.mal
+++ b/tests/incB.mal
@@ -3,10 +3,6 @@
(def! inc5 (fn* (a) ;; a comment after code
(+ 5 a)))
-;; Test map split across lines
-(def! mymap {"a"
- 1})
-
(prn "incB.mal finished")
"incB.mal return string"
diff --git a/tests/incC.mal b/tests/incC.mal
new file mode 100644
index 0000000..e6f5041
--- /dev/null
+++ b/tests/incC.mal
@@ -0,0 +1,6 @@
+(def! mymap {"a"
+ 1})
+
+(prn "incC.mal finished")
+"incC.mal return string"
+
diff --git a/tests/perf1.mal b/tests/perf1.mal
index 871e28f..73488f8 100644
--- a/tests/perf1.mal
+++ b/tests/perf1.mal
@@ -1,4 +1,5 @@
(load-file "../core.mal")
+(load-file "../perf.mal")
;;(prn "Start: basic macros performance test")
diff --git a/tests/perf2.mal b/tests/perf2.mal
index 3889191..c525baf 100644
--- a/tests/perf2.mal
+++ b/tests/perf2.mal
@@ -1,4 +1,5 @@
(load-file "../core.mal")
+(load-file "../perf.mal")
;;(prn "Start: basic math/recursion test")
diff --git a/tests/perf3.mal b/tests/perf3.mal
new file mode 100644
index 0000000..be66239
--- /dev/null
+++ b/tests/perf3.mal
@@ -0,0 +1,28 @@
+(load-file "../core.mal")
+(load-file "../perf.mal")
+
+;;(prn "Start: basic macros/atom test")
+
+(def! atm (atom (list 0 1 2 3 4 5 6 7 8 9)))
+
+(println "iters/s:"
+ (run-fn-for
+ (fn* []
+ (do
+ (or false nil false nil false nil false nil false nil (first @atm))
+ (cond false 1 nil 2 false 3 nil 4 false 5 nil 6 "else" (first @atm))
+ (-> (deref atm) rest rest rest rest rest rest first)
+ (swap! atm (fn* [a] (concat (rest a) (list (first a)))))))
+ 10))
+
+;;(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0)))
+;;(def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2)))))))
+;;
+;;(println "iters/s:"
+;; (run-fn-for
+;; (fn* []
+;; (do
+;; (sumdown 10)
+;; (fib 12)))
+;; 3))
+;;(prn "Done: basic macros/atom test")
diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal
index 0c715de..985b5fb 100644
--- a/tests/step1_read_print.mal
+++ b/tests/step1_read_print.mal
@@ -40,6 +40,8 @@ abc-def
;=>"abc\"def"
;;;"abc\ndef"
;;;;=>"abc\ndef"
+""
+;=>""
;; Testing read of lists
@@ -51,6 +53,49 @@ abc-def
;=>(+ 1 (+ 2 3))
( + 1 (+ 2 3 ) )
;=>(+ 1 (+ 2 3))
+(* 1 2)
+;=>(* 1 2)
+(** 1 2)
+;=>(** 1 2)
+
+;; Test commas as whitespace
+(1 2, 3,,,,),,
+;=>(1 2 3)
+
+;; Testing read of quoting
+'1
+;=>(quote 1)
+'(1 2 3)
+;=>(quote (1 2 3))
+`1
+;=>(quasiquote 1)
+`(1 2 3)
+;=>(quasiquote (1 2 3))
+~1
+;=>(unquote 1)
+~(1 2 3)
+;=>(unquote (1 2 3))
+~@(1 2 3)
+;=>(splice-unquote (1 2 3))
+
+;;
+;; Testing reader errors
+;;; TODO: fix these so they fail correctly
+(1 2
+; expected ')', got EOF
+[1 2
+; expected ']', got EOF
+"abc
+; expected '"', got EOF
+
+;;
+;; -------- Optional Functionality --------
+
+;; Testing keywords
+:kw
+;=>:kw
+(:kw1 :kw2 :kw3)
+;=>(:kw1 :kw2 :kw3)
;; Testing read of vectors
[+ 1 2]
@@ -71,22 +116,8 @@ abc-def
;=>{"a" {"b" {"c" 3}}}
{ "a" {"b" { "cde" 3 } }}
;=>{"a" {"b" {"cde" 3}}}
-
-;; Test commas as whitespace
-[1 2, 3,,,,],,
-;=>[1 2 3]
-
-;;
-;; Testing reader errors
-(1 2
-; expected ')', got EOF
-[1 2
-; expected ']', got EOF
-"abc
-; expected '"', got EOF
-
-;;
-;; -------- Optional Functionality --------
+{ :a {:b { :cde 3 } }}
+;=>{:a {:b {:cde 3}}}
;; Testing read of comments
;; whole line comment (not an exception)
@@ -95,23 +126,6 @@ abc-def
1; comment after expression
;=>1
-;; Testing read of quoting
-'1
-;=>(quote 1)
-'(1 2 3)
-;=>(quote (1 2 3))
-`1
-;=>(quasiquote 1)
-`(1 2 3)
-;=>(quasiquote (1 2 3))
-~1
-;=>(unquote 1)
-~(1 2 3)
-;=>(unquote (1 2 3))
-~@(1 2 3)
-;=>(splice-unquote (1 2 3))
-
-
;; Testing read of ^/metadata
^{"a" 1} [1 2 3]
;=>(with-meta [1 2 3] {"a" 1})
diff --git a/tests/step2_eval.mal b/tests/step2_eval.mal
index eb35592..e975910 100644
--- a/tests/step2_eval.mal
+++ b/tests/step2_eval.mal
@@ -11,6 +11,9 @@
(/ (- (+ 5 (* 2 3)) 3) 4)
;=>2
+(/ (- (+ 515 (* 222 311)) 302) 27)
+;=>2565
+
(abc 1 2 3)
; .*\'abc\' not found.*
@@ -23,3 +26,6 @@
{"a" (+ 7 8)}
;=>{"a" 15}
+
+{:a (+ 7 8)}
+;=>{:a 15}
diff --git a/tests/step3_env.mal b/tests/step3_env.mal
index 26372e6..8fb4c42 100644
--- a/tests/step3_env.mal
+++ b/tests/step3_env.mal
@@ -35,6 +35,12 @@ x
;;
;; -------- Optional Functionality --------
+;; Testing let* with vector bindings
+(let* [z 9] z)
+;=>9
+(let* [p (+ 2 3) q (+ 2 p)] (+ p q))
+;=>12
+
;; Testing vector evaluation
(let* (a 5 b 6) [3 4 a [b 7] 8])
;=>[3 4 5 [6 7] 8]
diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal
index 3e1c016..34966f5 100644
--- a/tests/step4_if_fn_do.mal
+++ b/tests/step4_if_fn_do.mal
@@ -14,6 +14,10 @@
;=>(1 2 3)
(count (list 1 2 3))
;=>3
+(count (list))
+;=>0
+(count nil)
+;=>0
(if (> (count (list 1 2 3)) 3) "yes" "no")
;=>"no"
(if (>= (count (list 1 2 3)) 3) "yes" "no")
@@ -39,6 +43,8 @@
;=>7
(if (list 1 2 3) 7 8)
;=>7
+(= (list) nil)
+;=>false
;; Testing 1-way if form
@@ -61,6 +67,10 @@
;=>false
(= 2 (+ 1 1))
;=>true
+(= nil 1)
+;=>false
+(= nil nil)
+;=>true
(> 2 1)
;=>true
@@ -331,10 +341,28 @@ a
;;
;; -------- Optional Functionality --------
+;; Testing keywords
+(= :abc :abc)
+;=>true
+(= :abc :def)
+;=>false
+(= :abc ":abc")
+;=>false
+
;; Testing vector truthiness
(if [] 7 8)
;=>7
+;; Testing vector functions
+(count [1 2 3])
+;=>3
+(empty? [1 2 3])
+;=>false
+(empty? [])
+;=>true
+(list? [4 5 6])
+;=>false
+
;; Testing vector equality
(= [] (list))
;=>true
@@ -353,3 +381,8 @@ a
(= "" [])
;=>false
+;; Testing vector parameter lists
+( (fn* [] 4) )
+;=>4
+( (fn* [f x] (f x)) (fn* [a] (+ 1 a)) 7)
+;=>8
diff --git a/tests/step6_file.mal b/tests/step6_file.mal
index 8198391..adee56d 100644
--- a/tests/step6_file.mal
+++ b/tests/step6_file.mal
@@ -1,3 +1,9 @@
+;;; TODO: really a step5 test
+;;
+;; Testing that (do (do)) not broken by TCO
+(do (do 1 2))
+;=>2
+
;; Testing read-string, eval and slurp
(read-string "(+ 2 3)")
@@ -22,8 +28,16 @@
;=>12
;;
+;; Testing that *ARGV* exists and is an empty list
+(list? *ARGV*)
+;=>true
+*ARGV*
+;=>()
+
+;;
;; -------- Optional Functionality --------
+;; Testing comments in a file
(load-file "../tests/incB.mal")
; "incB.mal finished"
;=>"incB.mal return string"
@@ -31,3 +45,18 @@
;=>11
(inc5 7)
;=>12
+
+;; Testing map literal across multiple lines in a file
+(load-file "../tests/incC.mal")
+mymap
+;=>{"a" 1}
+
+;;; TODO: really a step5 test
+;; Testing that vector params not broken by TCO
+(def! g (fn* [] 78))
+(g)
+;=>78
+(def! g (fn* [a] (+ a 78)))
+(g 3)
+;=>81
+
diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal
index 9166065..dae6cbd 100644
--- a/tests/step7_quote.mal
+++ b/tests/step7_quote.mal
@@ -1,3 +1,26 @@
+;; Testing cons function
+(cons 1 (list))
+;=>(1)
+(cons 1 (list 2))
+;=>(1 2)
+(cons 1 (list 2 3))
+;=>(1 2 3)
+(cons (list 1) (list 2 3))
+;=>((1) 2 3)
+
+;; Testing concat function
+(concat)
+;=>()
+(concat (list 1 2))
+;=>(1 2)
+(concat (list 1 2) (list 3 4))
+;=>(1 2 3 4)
+(concat (list 1 2) (list 3 4) (list 5 6))
+;=>(1 2 3 4 5 6)
+(concat (concat))
+;=>()
+
+
;; Testing regular quote
(quote 7)
;=>7
@@ -47,9 +70,6 @@
;=>(1 b 3)
`(1 ~b 3)
;=>(1 (1 "b" "d") 3)
-;;; TODO: fix this
-;;;`[1 ~b 3]
-;;;;=>[1 (1 "b" "d") 3]
;; Testing splice-unquote
@@ -59,9 +79,6 @@
;=>(1 c 3)
`(1 ~@c 3)
;=>(1 1 "b" "d" 3)
-;;; TODO: fix this
-;;;`[1 ~@c 3]
-;;;;=>[1 1 "b" "d" 3]
;; Testing symbol equality
@@ -73,3 +90,37 @@
;=>false
(= "abc" 'abc)
;=>false
+
+;;;;; Test quine
+;;; TODO: needs expect line length fix
+;;;((fn* [q] (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* [q] (quasiquote ((unquote q) (quote (unquote q)))))))
+;;;=>((fn* [q] (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* [q] (quasiquote ((unquote q) (quote (unquote q)))))))
+
+;;
+;; -------- Optional Functionality --------
+
+;; Testing cons, concat, first, rest with vectors
+
+(cons [1] [2 3])
+;=>([1] 2 3)
+(cons 1 [2 3])
+;=>(1 2 3)
+(concat [1 2] (list 3 4) [5 6])
+;=>(1 2 3 4 5 6)
+
+;; Testing unquote with vectors
+(def! a 8)
+;=>8
+`[1 a 3]
+;=>(1 a 3)
+;;; TODO: fix this
+;;;;=>[1 a 3]
+
+;; Testing splice-unquote with vectors
+(def! c '(1 "b" "d"))
+;=>(1 "b" "d")
+`[1 ~@c 3]
+;=>(1 1 "b" "d" 3)
+;;; TODO: fix this
+;;;;=>[1 1 "b" "d" 3]
+
diff --git a/tests/step8_macros.mal b/tests/step8_macros.mal
index 8940a89..cf8f5d1 100644
--- a/tests/step8_macros.mal
+++ b/tests/step8_macros.mal
@@ -1,26 +1,14 @@
-;; Testing cons function
-(cons 1 (list))
-;=>(1)
-(cons 1 (list 2))
-;=>(1 2)
-(cons 1 (list 2 3))
-;=>(1 2 3)
-(cons (list 1) (list 2 3))
-;=>((1) 2 3)
-
-;; Testing concat function
-(concat)
-;=>()
-(concat (list 1 2))
-;=>(1 2)
-(concat (list 1 2) (list 3 4))
-;=>(1 2 3 4)
-(concat (list 1 2) (list 3 4) (list 5 6))
-;=>(1 2 3 4 5 6)
-(concat (concat))
-;=>()
+;; Testing nth, first and rest functions
+
+(nth '(1) 0)
+;=>1
+(nth '(1 2) 1)
+;=>2
+(def! x "x")
+(def! x (nth '(1 2) 2))
+x
+;=>"x"
-;; Testing first function
(first '())
;=>nil
(first '(6))
@@ -28,7 +16,6 @@
(first '(7 8 9))
;=>7
-;; Testing rest function
(rest '())
;=>()
(rest '(6))
@@ -37,6 +24,13 @@
;=>(8 9)
+;; Testing non-macro function
+(not (= 1 1))
+;=>false
+;;; This should fail if it is a macro
+(not (= 1 2))
+;=>true
+
;; Testing trivial macros
(defmacro! one (fn* () 1))
@@ -58,6 +52,39 @@
(unless2 true 7 8)
;=>8
+;; Testing or macro
+(or)
+;=>nil
+(or 1)
+;=>1
+(or 1 2 3 4)
+;=>1
+(or false 2)
+;=>2
+(or false nil 3)
+;=>3
+(or false nil false false nil 4)
+;=>4
+(or false nil 3 false nil 4)
+;=>3
+
+;; Testing cond macro
+
+(cond)
+;=>nil
+(cond true 7)
+;=>7
+(cond true 7 true 8)
+;=>7
+(cond false 7 true 8)
+;=>8
+(cond false 7 false 8 "else" 9)
+;=>9
+(cond false 7 (= 2 2) 8 "else" 9)
+;=>8
+(cond false 7 false 8 false 9)
+;=>nil
+
;; Testing macroexpand
(macroexpand (unless2 2 3 4))
;=>(if (not 2) 3 4)
@@ -82,22 +109,6 @@
(and 1 2 3 4 false 5)
;=>false
-;; Testing or macro
-(or)
-;=>nil
-(or 1)
-;=>1
-(or 1 2 3 4)
-;=>1
-(or false 2)
-;=>2
-(or false nil 3)
-;=>3
-(or false nil false false nil 4)
-;=>4
-(or false nil 3 false nil 4)
-;=>3
-
;; Testing -> macro
(-> 7)
@@ -111,23 +122,6 @@
(-> (list 7 8 9) rest (rest) first (+ 7))
;=>16
-;; Testing cond macro
-
-(cond)
-;=>nil
-(cond true 7)
-;=>7
-(cond true 7 true 8)
-;=>7
-(cond false 7 true 8)
-;=>8
-(cond false 7 false 8 "else" 9)
-;=>9
-(cond false 7 (= 2 2) 8 "else" 9)
-;=>8
-(cond false 7 false 8 false 9)
-;=>nil
-
;; Testing EVAL in let*
(let* (x (or nil "yes")) x)
@@ -136,14 +130,17 @@
;;
;; -------- Optional Functionality --------
-;; Testing cons, concat, first, rest with vectors
+;; Testing nth, first, rest with vectors
+
+(nth [1] 0)
+;=>1
+(nth [1 2] 1)
+;=>2
+(def! x "x")
+(def! x (nth [1 2] 2))
+x
+;=>"x"
-(cons [1] [2 3])
-;=>([1] 2 3)
-(cons 1 [2 3])
-;=>(1 2 3)
-(concat [1 2] (list 3 4) [5 6])
-;=>(1 2 3 4 5 6)
(first [])
;=>nil
(first [10])
diff --git a/tests/stepA_more.mal b/tests/step9_try.mal
index 7b7dac5..168b632 100644
--- a/tests/stepA_more.mal
+++ b/tests/step9_try.mal
@@ -1,23 +1,30 @@
;;
;; Testing try*/catch*
+(try* 123 (catch* e 456))
+;=>123
+
(try* (abc 1 2) (catch* exc (prn "exc is:" exc))))
; "exc is:" "'abc' not found"
;=>nil
;;;TODO: fix so long lines don't trigger ANSI escape codes ;;;(try*
-;;;(try* (throw {"data" "foo"}) (catch* exc (do (prn "exc is:" exc) 7))) ;;;;
-;;;; "exc is:" {"data" "foo"} ;;;;=>7
+;;;(try* (throw ["data" "foo"]) (catch* exc (do (prn "exc is:" exc) 7))) ;;;;
+;;;; "exc is:" ["data" "foo"] ;;;;=>7
;;;;=>7
-(try* (throw {"data" "foo"}) (catch* exc (do (prn "err:" exc) 7)))
-; "err:" {"data" "foo"}
+(try* (throw (list "data" "foo")) (catch* exc (do (prn "err:" exc) 7)))
+; "err:" ("data" "foo")
;=>7
(try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7)))
; "exc:" "my exception"
;=>7
+;;; Test that throw is a function:
+(try* (map throw [7]) (catch* exc exc))
+;=>7
+
;;
;; Testing builtin functions
@@ -51,6 +58,8 @@
;=>9
(apply prn (list 1 2 "3" (list)))
; 1 2 "3" ()
+(apply prn 1 2 (list "3" (list)))
+; 1 2 "3" ()
;=>nil
@@ -61,6 +70,8 @@
;=>6
(map double nums)
;=>(2 4 6)
+(map (fn* (x) (symbol? x)) (list 1 (symbol "two") "three"))
+;=>(false true false)
;;
;; Testing read-str and eval
@@ -84,7 +95,26 @@
;=>"\"hello\""
;;
-;; -------- Optional Functionality --------
+;; ------- Optional Functionality ----------
+;; ------- (Needed for self-hosting) -------
+
+;; Testing symbol and keyword functions
+(symbol? :abc)
+;=>false
+(symbol? 'abc)
+;=>true
+(symbol? "abc")
+;=>false
+(symbol? (symbol "abc"))
+;=>true
+(keyword? :abc)
+;=>true
+(keyword? 'abc)
+;=>false
+(keyword? "abc")
+;=>false
+(keyword? (keyword "abc"))
+;=>true
;; Testing sequential? function
@@ -99,6 +129,11 @@
(sequential? "abc")
;=>false
+
+;; Testing map function with vectors
+(map (fn* (a) (* 2 a)) [1 2 3])
+;=>(2 4 6)
+
;; Testing vector functions
(vector? [10 11])
@@ -108,31 +143,16 @@
(vector 3 4 5)
;=>[3 4 5]
-;; Testing conj function
-(conj (list) 1)
-;=>(1)
-(conj (list 1) 2)
-;=>(2 1)
-(conj (list 2 3) 4)
-;=>(4 2 3)
-(conj (list 2 3) 4 5 6)
-;=>(6 5 4 2 3)
-(conj (list 1) (list 2 3))
-;=>((2 3) 1)
-
-(conj [] 1)
-;=>[1]
-(conj [1] 2)
-;=>[1 2]
-(conj [2 3] 4)
-;=>[2 3 4]
-(conj [2 3] 4 5 6)
-;=>[2 3 4 5 6]
-(conj [1] [2 3])
-;=>[1 [2 3]]
-
+(map? {})
+;=>true
+(map? '())
+;=>false
(map? [])
;=>false
+(map? 'abc)
+;=>false
+(map? :abc)
+;=>false
;;
;; Testing hash-maps
@@ -179,9 +199,18 @@
(contains? hm2 "a")
;=>true
+
+;;; TODO: fix. Clojure returns nil but this breaks mal impl
+(keys hm1)
+;=>()
+
(keys hm2)
;=>("a")
+;;; TODO: fix. Clojure returns nil but this breaks mal impl
+(vals hm1)
+;=>()
+
(vals hm2)
;=>(1)
@@ -206,36 +235,43 @@
(count (keys hm3))
;=>2
+;; Testing keywords as hash-map keys
+(get {:abc 123} :abc)
+;=>123
+(contains? {:abc 123} :abc)
+;=>true
+(contains? {:abcd 123} :abc)
+;=>false
+(assoc {} :bcd 234)
+;=>{:bcd 234}
+(dissoc {:cde 345 :fgh 456} :cde)
+;=>{:fgh 456}
+(keyword? (nth (keys {:abc 123 :def 456}) 0))
+;=>true
+;;; TODO: support : in strings in make impl
+;;;(keyword? (nth (keys {":abc" 123 ":def" 456}) 0))
+;;;;=>false
+(keyword? (nth (vals {"a" :abc "b" :def}) 0))
+;=>true
+
+
;;
-;; Testing metadata
-(meta [1 2 3])
-;=>nil
+;; Testing metadata on functions
+
+;;
+;; Testing metadata on mal functions
(meta (fn* (a) a))
;=>nil
-(with-meta [1 2 3] {"a" 1})
-;=>[1 2 3]
-
-(meta (with-meta [1 2 3] {"a" 1}))
-;=>{"a" 1}
+(meta (with-meta (fn* (a) a) {"b" 1}))
+;=>{"b" 1}
-(meta (with-meta [1 2 3] "abc"))
+(meta (with-meta (fn* (a) a) "abc"))
;=>"abc"
-(meta (with-meta (list 1 2 3) {"a" 1}))
-;=>{"a" 1}
-
-(meta (with-meta {"abc" 123} {"a" 1}))
-;=>{"a" 1}
-
-;;; Not actually supported by Clojure
-;;;(meta (with-meta (atom 7) {"a" 1}))
-;;;;=>{"a" 1}
-
-(def! l-wm (with-meta [4 5 6] {"b" 2}))
-;=>[4 5 6]
+(def! l-wm (with-meta (fn* (a) a) {"b" 2}))
(meta l-wm)
;=>{"b" 2}
@@ -244,7 +280,6 @@
(meta l-wm)
;=>{"b" 2}
-;; Testing metadata on functions
(def! f-wm (with-meta (fn* [a] (+ 1 a)) {"abc" 1}))
(meta f-wm)
;=>{"abc" 1}
@@ -254,20 +289,10 @@
(meta f-wm)
;=>{"abc" 1}
-
(def! f-wm2 ^{"abc" 1} (fn* [a] (+ 1 a)))
(meta f-wm2)
;=>{"abc" 1}
-;; Testing metadata on builtin functions
-(meta +)
-;=>nil
-(def! f-wm3 ^{"def" 2} +)
-(meta f-wm3)
-;=>{"def" 2}
-(meta +)
-;=>nil
-
;;
;; Make sure closures and metadata co-exist
(def! gen-plusX (fn* (x) (with-meta (fn* (b) (+ x b)) {"meta" 1})))
@@ -335,3 +360,78 @@
(f)
;=>9
+
+;;
+;; ------- Optional Functionality --------------
+;; ------- (Not needed for self-hosting) -------
+
+;;
+;; Testing conj function
+(conj (list) 1)
+;=>(1)
+(conj (list 1) 2)
+;=>(2 1)
+(conj (list 2 3) 4)
+;=>(4 2 3)
+(conj (list 2 3) 4 5 6)
+;=>(6 5 4 2 3)
+(conj (list 1) (list 2 3))
+;=>((2 3) 1)
+
+(conj [] 1)
+;=>[1]
+(conj [1] 2)
+;=>[1 2]
+(conj [2 3] 4)
+;=>[2 3 4]
+(conj [2 3] 4 5 6)
+;=>[2 3 4 5 6]
+(conj [1] [2 3])
+;=>[1 [2 3]]
+
+
+;;
+;; Testing metadata on collections
+
+(meta [1 2 3])
+;=>nil
+
+(with-meta [1 2 3] {"a" 1})
+;=>[1 2 3]
+
+(meta (with-meta [1 2 3] {"a" 1}))
+;=>{"a" 1}
+
+(meta (with-meta [1 2 3] "abc"))
+;=>"abc"
+
+(meta (with-meta (list 1 2 3) {"a" 1}))
+;=>{"a" 1}
+
+(meta (with-meta {"abc" 123} {"a" 1}))
+;=>{"a" 1}
+
+;;; Not actually supported by Clojure
+;;;(meta (with-meta (atom 7) {"a" 1}))
+;;;;=>{"a" 1}
+
+(def! l-wm (with-meta [4 5 6] {"b" 2}))
+;=>[4 5 6]
+(meta l-wm)
+;=>{"b" 2}
+
+(meta (with-meta l-wm {"new_meta" 123}))
+;=>{"new_meta" 123}
+(meta l-wm)
+;=>{"b" 2}
+
+;;
+;; Testing metadata on builtin functions
+(meta +)
+;=>nil
+(def! f-wm3 ^{"def" 2} +)
+(meta f-wm3)
+;=>{"def" 2}
+(meta +)
+;=>nil
+
diff --git a/tests/test.txt b/tests/test.txt
new file mode 100644
index 0000000..0f24bc0
--- /dev/null
+++ b/tests/test.txt
@@ -0,0 +1 @@
+A line of text
diff --git a/vb/Makefile b/vb/Makefile
new file mode 100644
index 0000000..25d9b96
--- /dev/null
+++ b/vb/Makefile
@@ -0,0 +1,52 @@
+#####################
+
+DEBUG =
+
+TESTS =
+
+SOURCES_BASE = readline.vb types.vb reader.vb printer.vb
+SOURCES_LISP = env.vb core.vb stepA_mal.vb
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#####################
+
+SRCS = step0_repl.vb step1_read_print.vb step2_eval.vb \
+ step3_env.vb step4_if_fn_do.vb step5_tco.vb step6_file.vb \
+ step7_quote.vb step8_macros.vb step9_try.vb stepA_mal.vb
+
+LIB_CS_SRCS = getline.cs
+LIB_VB_SRCS = $(filter-out step%,$(filter %.vb,$(SOURCES)))
+
+FLAGS = $(if $(strip $(DEBUG)),-debug:full,)
+
+#####################
+
+all: mal.exe $(patsubst %.vb,%.exe,$(SRCS))
+
+mal.exe: $(patsubst %.vb,%.exe,$(word $(words $(SOURCES)),$(SOURCES)))
+ cp $< $@
+
+mal_cs.dll: $(LIB_CS_SRCS)
+ mcs $(FLAGS) -target:library $+ -out:$@
+
+mal_vb.dll: mal_cs.dll $(LIB_VB_SRCS)
+ vbnc $(FLAGS) -target:library -r:mal_cs.dll $(LIB_VB_SRCS) -out:$@
+
+%.exe: %.vb mal_vb.dll
+ vbnc $(FLAGS) -r:mal_vb.dll -r:mal_cs.dll $<
+
+clean:
+ rm -f *.dll *.exe *.mdb
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ ./$@ || exit 1; \
diff --git a/vb/core.vb b/vb/core.vb
new file mode 100644
index 0000000..771d0cc
--- /dev/null
+++ b/vb/core.vb
@@ -0,0 +1,457 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports MalVal = Mal.types.MalVal
+Imports MalConstant = Mal.types.MalConstant
+Imports MalInt = Mal.types.MalInt
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalString = Mal.types.MalString
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalAtom = Mal.types.MalAtom
+Imports MalFunc = Mal.types.MalFunc
+
+Namespace Mal
+ Public Class core
+ Shared Nil As MalConstant = Mal.types.Nil
+ Shared MalTrue As MalConstant = Mal.types.MalTrue
+ Shared MalFalse As MalConstant = Mal.types.MalFalse
+
+ ' Errors/Exceptions
+ Shared Function mal_throw(a As MalList) As MalVal
+ throw New Mal.types.MalException(a(0))
+ End Function
+
+ ' General functions
+ Shared Function equal_Q(a As MalList) As MalVal
+ If Mal.types._equal_Q(a(0), a(1)) Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ ' Scalar functions
+ Shared Function nil_Q(a As MalList) As MalVal
+ If a(0) Is Nil Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function true_Q(a As MalList) As MalVal
+ If a(0) Is MalTrue Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function false_Q(a As MalList) As MalVal
+ If a(0) Is MalFalse Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function symbol(a As MalList) As MalVal
+ return new MalSymbol(DirectCast(a(0),MalString))
+ End Function
+
+ Shared Function symbol_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalSymbol Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function keyword(a As MalList) As MalVal
+ Dim s As String = DirectCast(a(0),MalString).getValue()
+ return new MalString(ChrW(&H029e) & s)
+ End Function
+
+ Shared Function keyword_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalString Then
+ Dim s As String = DirectCast(a(0),MalString).getValue()
+ If s.Substring(0,1) = Strings.ChrW(&H029e) Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ Else
+ return MalFalse
+ End If
+ End Function
+
+
+ ' Number functions
+ Shared Function lt(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) < DirectCast(a(1),MalInt)
+ End Function
+ Shared Function lte(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) <= DirectCast(a(1),MalInt)
+ End Function
+ Shared Function gt(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) > DirectCast(a(1),MalInt)
+ End Function
+ Shared Function gte(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) >= DirectCast(a(1),MalInt)
+ End Function
+ Shared Function plus(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) + DirectCast(a(1),MalInt)
+ End Function
+ Shared Function minus(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) - DirectCast(a(1),MalInt)
+ End Function
+ Shared Function mult(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) * DirectCast(a(1),MalInt)
+ End Function
+ Shared Function div(a As MalList) As MalVal
+ return DirectCast(a(0),MalInt) / DirectCast(a(1),MalInt)
+ End Function
+
+ Shared Function time_ms(a As MalList) As MalVal
+ return New MalInt(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)
+ End Function
+
+ ' String functions
+ Shared Function pr_str(a As MalList) As MalVal
+ return New MalString(printer._pr_str_args(a, " ", true))
+ End Function
+
+ Shared Function str(a As MalList) As MalVal
+ return new MalString(printer._pr_str_args(a, "", false))
+ End Function
+
+ Shared Function prn(a As MalList) As MalVal
+ Console.WriteLine(printer._pr_str_args(a, " ", true))
+ return Nil
+ End Function
+
+ Shared Function println(a As MalList) As MalVal
+ Console.WriteLine(printer._pr_str_args(a, " ", false))
+ return Nil
+ End Function
+
+ Shared Function mal_readline(a As MalList) As MalVal
+ Dim line As String
+ line = readline.Readline(DirectCast(a(0),MalString).getValue())
+ If line Is Nothing Then
+ return types.Nil
+ Else
+ return New MalString(line)
+ End If
+ End Function
+
+ Shared Function read_string(a As MalList) As MalVal
+ return reader.read_str(DirectCast(a(0),MalString).getValue())
+ End Function
+
+ Shared Function slurp(a As MalList) As MalVal
+ return New MalString(File.ReadAllText(DirectCast(a(0),MalString).getValue()))
+ End Function
+
+
+ ' List/Vector functions
+
+ Shared Function list(a As MalList) As MalVal
+ return New MalList(a.getValue())
+ End Function
+
+ Shared Function list_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalList And Not TypeOf a(0) Is MalVector Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function vector(a As MalList) As MalVal
+ return New MalVector(a.getValue())
+ End Function
+
+ Shared Function vector_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalVector Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ ' HashMap functions
+ Shared Function hash_map(a As MalList) As MalVal
+ return New MalHashMap(a)
+ End Function
+
+ Shared Function hash_map_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalHashMap Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function contains_Q(a As MalList) As MalVal
+ Dim key As String = DirectCast(a(1),MalString).getValue()
+ Dim dict As Dictionary(Of String,MalVal) = DirectCast(a(0),MalHashMap).getValue()
+ If dict.ContainsKey(key) Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function assoc(a As MalList) As MalVal
+ Dim new_hm As MalHashMap = DirectCast(a(0),MalHashMap).copy()
+ return new_hm.assoc_BANG(DirectCast(a.slice(1),MalList))
+ End Function
+
+ Shared Function dissoc(a As MalList) As MalVal
+ Dim new_hm As MalHashMap = DirectCast(a(0),MalHashMap).copy()
+ return new_hm.dissoc_BANG(DirectCast(a.slice(1),MalList))
+ End Function
+
+ Shared Function do_get(a As MalList) As MalVal
+ Dim k As String = DirectCast(a(1),MalString).getValue()
+ If a(0) Is Nil Then
+ return Nil
+ Else
+ Dim dict As Dictionary(Of String,MalVal) = DirectCast(a(0),MalHashMap).getValue()
+ If dict.ContainsKey(k) Then
+ return dict(k)
+ Else
+ return Nil
+ End If
+ End If
+ End Function
+
+ Shared Function keys(a As MalList) As MalVal
+ Dim dict As Dictionary(Of String,MalVal) = DirectCast(a(0),MalHashMap).getValue()
+ Dim key_lst As MalList = New MalList()
+ For Each key As String in dict.Keys
+ key_lst.conj_BANG(new MalString(key))
+ Next
+ return key_lst
+ End Function
+
+ Shared Function vals(a As MalList) As MalVal
+ Dim dict As Dictionary(Of String,MalVal) = DirectCast(a(0),MalHashMap).getValue()
+ Dim val_lst As MalList = New MalList()
+ For Each val As MalVal In dict.Values
+ val_lst.conj_BANG(val)
+ Next
+ return val_lst
+ End Function
+
+ ' Sequence functions
+ Shared Function sequential_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalList Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function cons(a As MalList) As MalVal
+ Dim lst As New List(Of MalVal)
+ lst.Add(a(0))
+ lst.AddRange(DirectCast(a(1),MalList).getValue())
+ return DirectCast(New MalList(lst),MalVal)
+ End Function
+
+ Shared Function concat(a As MalList) As MalVal
+ If a.size() = 0 Then
+ return new MalList()
+ End If
+ Dim lst As New List(Of MalVal)
+ lst.AddRange(DirectCast(a(0),MalList).getValue())
+ for i As Integer = 1 To a.size()-1
+ lst.AddRange(DirectCast(a(i),MalList).getValue())
+ Next
+ return DirectCast(new MalList(lst),MalVal)
+ End Function
+
+ Shared Function nth(a As MalList) As MalVal
+ Dim idx As Integer = DirectCast(a(1),MalInt).getValue()
+ If (idx < DirectCast(a(0),MalList).size()) Then
+ return DirectCast(a(0),MalList)( idx )
+ Else
+ throw new Mal.types.MalException(
+ "nth: index out of range")
+ End If
+ End Function
+
+ Shared Function first(a As MalList) As MalVal
+ return DirectCast(a(0),MalList)(0)
+ End Function
+
+ Shared Function rest(a As MalList) As MalVal
+ return DirectCast(a(0),MalList).rest()
+ End Function
+
+ Shared Function empty_Q(a As MalList) As MalVal
+ If DirectCast(a(0),MalList).size() = 0 Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function count(a As MalList) As MalVal
+ If a(0) Is Nil Then
+ return new MalInt(0)
+ Else
+ return new MalInt(DirectCast(a(0),MalList).size())
+ End If
+ End Function
+
+ Shared Function conj(a As MalList) As MalVal
+ Dim src_lst As List(Of MalVal) = DirectCast(a(0),MalList).getValue()
+ Dim new_lst As New List(Of MalVal)
+ new_lst.AddRange(src_lst)
+ If TypeOf a(0) Is MalVector Then
+ For i As Integer = 1 To a.size()-1
+ new_lst.Add(a(i))
+ Next
+ return new MalVector(new_lst)
+ Else
+ For i As Integer = 1 To a.size()-1
+ new_lst.Insert(0, a(i))
+ Next
+ return new MalList(new_lst)
+ End If
+ End Function
+
+
+ ' General list related functions
+ Shared Function apply(a As MalList) As MalVal
+ Dim f As MalFunc = DirectCast(a(0),MalFunc)
+ Dim lst As New List(Of MalVal)
+ lst.AddRange(a.slice(1,a.size()-1).getValue())
+ lst.AddRange(DirectCast(a(a.size()-1),MalList).getValue())
+ return f.apply(New MalList(lst))
+ End Function
+
+ Shared Function map(a As MalList) As MalVal
+ Dim f As MalFunc = DirectCast(a(0),MalFunc)
+ Dim src_lst As List(Of MalVal) = DirectCast(a(1),MalList).getValue()
+ Dim new_lst As New List(Of MalVal)
+ for i As Integer = 0 To src_lst.Count-1
+ new_lst.Add(f.apply(New MalList(src_lst(i))))
+ Next
+ return new MalList(new_lst)
+ End Function
+
+
+ ' Metadata functions
+ Shared Function atom(a As MalList) As MalVal
+ return new MalAtom(a(0))
+ End Function
+
+ Shared Function meta(a As MalList) As MalVal
+ return a(0).getMeta()
+ End Function
+
+ Shared Function with_meta(a As MalList) As MalVal
+ return DirectCast(a(0),MalVal).copy().setMeta(a(1))
+ End Function
+
+
+ ' Atom functions
+ Shared Function atom_Q(a As MalList) As MalVal
+ If TypeOf a(0) Is MalAtom Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Function
+
+ Shared Function deref(a As MalList) As MalVal
+ return DirectCast(a(0),MalAtom).getValue()
+ End Function
+
+ Shared Function reset_BANG(a As MalList) As MalVal
+ return DirectCast(a(0),MalAtom).setValue(a(1))
+ End Function
+
+ Shared Function swap_BANG(a As MalList) As MalVal
+ Dim atm As MalAtom = DirectCast(a(0),MalAtom)
+ Dim f As MalFunc = DirectCast(a(1),MalFunc)
+ Dim new_lst As New List(Of MalVal)
+ new_lst.Add(atm.getValue())
+ new_lst.AddRange(DirectCast(a.slice(2),MalList).getValue())
+ return atm.setValue(f.apply(New MalList(new_lst)))
+ End Function
+
+
+
+ Shared Function ns As Dictionary(Of String, MalVal)
+ Dim ns As New Dictionary(Of String, MalVal)
+
+ ns.Add("=", New MalFunc(AddressOf equal_Q))
+ ns.Add("throw", New MalFunc(AddressOf mal_throw))
+ ns.Add("nil?", New MalFunc(AddressOf nil_Q))
+ ns.Add("true?", New MalFunc(AddressOf true_Q))
+ ns.Add("false?", New MalFunc(AddressOf false_Q))
+ ns.Add("symbol", new MalFunc(AddressOf symbol))
+ ns.Add("symbol?", New MalFunc(AddressOf symbol_Q))
+ ns.Add("keyword", new MalFunc(AddressOf keyword))
+ ns.Add("keyword?", New MalFunc(AddressOf keyword_Q))
+
+ ns.Add("pr-str",New MalFunc(AddressOf pr_str))
+ ns.Add("str", New MalFunc(AddressOf str))
+ ns.Add("prn", New MalFunc(AddressOf prn))
+ ns.Add("println", New MalFunc(AddressOf println))
+ ns.Add("readline", New MalFunc(AddressOf mal_readline))
+ ns.Add("read-string", New MalFunc(AddressOf read_string))
+ ns.Add("slurp", New MalFunc(AddressOf slurp))
+ ns.Add("<", New MalFunc(AddressOf lt))
+ ns.Add("<=", New MalFunc(AddressOf lte))
+ ns.Add(">", New MalFunc(AddressOf gt))
+ ns.Add(">=", New MalFunc(AddressOf gte))
+ ns.Add("+", New MalFunc(AddressOf plus))
+ ns.Add("-", New MalFunc(AddressOf minus))
+ ns.Add("*", New MalFunc(AddressOf mult))
+ ns.Add("/", New MalFunc(AddressOf div))
+ ns.Add("time-ms", New MalFunc(AddressOf time_ms))
+
+ ns.Add("list", New MalFunc(AddressOf list))
+ ns.Add("list?", New MalFunc(AddressOf list_Q))
+ ns.Add("vector", new MalFunc(AddressOf vector))
+ ns.Add("vector?", New MalFunc(AddressOf vector_Q))
+ ns.Add("hash-map", new MalFunc(AddressOf hash_map))
+ ns.Add("map?", New MalFunc(AddressOf hash_map_Q))
+ ns.Add("contains?", New MalFunc(AddressOf contains_Q))
+ ns.Add("assoc", New MalFunc(AddressOf assoc))
+ ns.Add("dissoc", New MalFunc(AddressOf dissoc))
+ ns.Add("get", New MalFunc(AddressOf do_get))
+ ns.Add("keys", New MalFunc(AddressOf keys))
+ ns.Add("vals", New MalFunc(AddressOf vals))
+
+ ns.Add("sequential?", New MalFunc(AddressOf sequential_Q))
+ ns.Add("cons", New MalFunc(AddressOf cons))
+ ns.Add("concat", New MalFunc(AddressOf concat))
+ ns.Add("nth", New MalFunc(AddressOf nth))
+ ns.Add("first", New MalFunc(AddressOf first))
+ ns.Add("rest", New MalFunc(AddressOf rest))
+ ns.Add("empty?", New MalFunc(AddressOf empty_Q))
+ ns.Add("count",New MalFunc(AddressOf count))
+ ns.Add("conj", New MalFunc(AddressOf conj))
+ ns.Add("apply", New MalFunc(AddressOf apply))
+ ns.Add("map", New MalFunc(AddressOf map))
+
+ ns.Add("with-meta", New MalFunc(AddressOf with_meta))
+ ns.Add("meta", New MalFunc(AddressOf meta))
+ ns.Add("atom", new MalFunc(AddressOf atom))
+ ns.Add("atom?", New MalFunc(AddressOf atom_Q))
+ ns.Add("deref", New MalFunc(AddressOf deref))
+ ns.Add("reset!", New MalFunc(AddressOf reset_BANG))
+ ns.Add("swap!", New MalFunc(AddressOf swap_BANG))
+ return ns
+ End Function
+ End Class
+End Namespace
diff --git a/vb/env.vb b/vb/env.vb
new file mode 100644
index 0000000..a2c4628
--- /dev/null
+++ b/vb/env.vb
@@ -0,0 +1,55 @@
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+
+Namespace Mal
+ Public Class env
+ Public Class Env
+ Dim outer As Env = Nothing
+ Dim data As Dictionary(Of String, MalVal) = New Dictionary(Of String, MalVal)
+
+ Public Sub New(new_outer As Env)
+ outer = new_outer
+ End Sub
+ Public Sub New(new_outer As Env, binds As MalList, exprs As MalList)
+ outer = new_outer
+ For i As Integer = 0 To binds.size()-1
+ Dim sym As String = DirectCast(binds.nth(i),MalSymbol).getName()
+ If sym = "&" Then
+ data(DirectCast(binds.nth(i+1),MalSymbol).getName()) = exprs.slice(i)
+ Exit For
+ Else
+ data(sym) = exprs.nth(i)
+ End If
+ Next
+ End Sub
+
+ Public Function find(key As MalSymbol) As Env
+ If data.ContainsKey(key.getName()) Then
+ return Me
+ Else If outer IsNot Nothing Then
+ return outer.find(key)
+ Else
+ return Nothing
+ End If
+ End Function
+
+ Public Function do_get(key As MalSymbol) As MalVal
+ Dim e As Env = find(key)
+ If e Is Nothing Then
+ throw New Mal.types.MalException(
+ "'" & key.getName() & "' not found")
+ Else
+ return e.data(key.getName())
+ End If
+ End Function
+
+ Public Function do_set(key As MalSymbol, value As MalVal) As Env
+ data(key.getName()) = value
+ return Me
+ End Function
+ End Class
+ End Class
+End Namespace
diff --git a/vb/getline.cs b/vb/getline.cs
new file mode 100644
index 0000000..c11a11d
--- /dev/null
+++ b/vb/getline.cs
@@ -0,0 +1,1089 @@
+//
+// getline.cs: A command line editor
+//
+// Authors:
+// Miguel de Icaza (miguel@novell.com)
+//
+// Copyright 2008 Novell, Inc.
+//
+// Dual-licensed under the terms of the MIT X11 license or the
+// Apache License 2.0
+//
+// USE -define:DEMO to build this as a standalone file and test it
+//
+// TODO:
+// Enter an error (a = 1); Notice how the prompt is in the wrong line
+// This is caused by Stderr not being tracked by System.Console.
+// Completion support
+// Why is Thread.Interrupt not working? Currently I resort to Abort which is too much.
+//
+// Limitations in System.Console:
+// Console needs SIGWINCH support of some sort
+// Console needs a way of updating its position after things have been written
+// behind its back (P/Invoke puts for example).
+// System.Console needs to get the DELETE character, and report accordingly.
+//
+
+using System;
+using System.Text;
+using System.IO;
+using System.Threading;
+using System.Reflection;
+
+namespace Mono.Terminal {
+
+ public class LineEditor {
+
+ public class Completion {
+ public string [] Result;
+ public string Prefix;
+
+ public Completion (string prefix, string [] result)
+ {
+ Prefix = prefix;
+ Result = result;
+ }
+ }
+
+ public delegate Completion AutoCompleteHandler (string text, int pos);
+
+ //static StreamWriter log;
+
+ // The text being edited.
+ StringBuilder text;
+
+ // The text as it is rendered (replaces (char)1 with ^A on display for example).
+ StringBuilder rendered_text;
+
+ // The prompt specified, and the prompt shown to the user.
+ string prompt;
+ string shown_prompt;
+
+ // The current cursor position, indexes into "text", for an index
+ // into rendered_text, use TextToRenderPos
+ int cursor;
+
+ // The row where we started displaying data.
+ int home_row;
+
+ // The maximum length that has been displayed on the screen
+ int max_rendered;
+
+ // If we are done editing, this breaks the interactive loop
+ bool done = false;
+
+ // The thread where the Editing started taking place
+ Thread edit_thread;
+
+ // Our object that tracks history
+ History history;
+
+ // The contents of the kill buffer (cut/paste in Emacs parlance)
+ string kill_buffer = "";
+
+ // The string being searched for
+ string search;
+ string last_search;
+
+ // whether we are searching (-1= reverse; 0 = no; 1 = forward)
+ int searching;
+
+ // The position where we found the match.
+ int match_at;
+
+ // Used to implement the Kill semantics (multiple Alt-Ds accumulate)
+ KeyHandler last_handler;
+
+ delegate void KeyHandler ();
+
+ struct Handler {
+ public ConsoleKeyInfo CKI;
+ public KeyHandler KeyHandler;
+
+ public Handler (ConsoleKey key, KeyHandler h)
+ {
+ CKI = new ConsoleKeyInfo ((char) 0, key, false, false, false);
+ KeyHandler = h;
+ }
+
+ public Handler (char c, KeyHandler h)
+ {
+ KeyHandler = h;
+ // Use the "Zoom" as a flag that we only have a character.
+ CKI = new ConsoleKeyInfo (c, ConsoleKey.Zoom, false, false, false);
+ }
+
+ public Handler (ConsoleKeyInfo cki, KeyHandler h)
+ {
+ CKI = cki;
+ KeyHandler = h;
+ }
+
+ public static Handler Control (char c, KeyHandler h)
+ {
+ return new Handler ((char) (c - 'A' + 1), h);
+ }
+
+ public static Handler Alt (char c, ConsoleKey k, KeyHandler h)
+ {
+ ConsoleKeyInfo cki = new ConsoleKeyInfo ((char) c, k, false, true, false);
+ return new Handler (cki, h);
+ }
+ }
+
+ /// <summary>
+ /// Invoked when the user requests auto-completion using the tab character
+ /// </summary>
+ /// <remarks>
+ /// The result is null for no values found, an array with a single
+ /// string, in that case the string should be the text to be inserted
+ /// for example if the word at pos is "T", the result for a completion
+ /// of "ToString" should be "oString", not "ToString".
+ ///
+ /// When there are multiple results, the result should be the full
+ /// text
+ /// </remarks>
+ public AutoCompleteHandler AutoCompleteEvent;
+
+ static Handler [] handlers;
+
+ public LineEditor (string name) : this (name, 10) { }
+
+ public LineEditor (string name, int histsize)
+ {
+ handlers = new Handler [] {
+ new Handler (ConsoleKey.Home, CmdHome),
+ new Handler (ConsoleKey.End, CmdEnd),
+ new Handler (ConsoleKey.LeftArrow, CmdLeft),
+ new Handler (ConsoleKey.RightArrow, CmdRight),
+ new Handler (ConsoleKey.UpArrow, CmdHistoryPrev),
+ new Handler (ConsoleKey.DownArrow, CmdHistoryNext),
+ new Handler (ConsoleKey.Enter, CmdDone),
+ new Handler (ConsoleKey.Backspace, CmdBackspace),
+ new Handler (ConsoleKey.Delete, CmdDeleteChar),
+ new Handler (ConsoleKey.Tab, CmdTabOrComplete),
+
+ // Emacs keys
+ Handler.Control ('A', CmdHome),
+ Handler.Control ('E', CmdEnd),
+ Handler.Control ('B', CmdLeft),
+ Handler.Control ('F', CmdRight),
+ Handler.Control ('P', CmdHistoryPrev),
+ Handler.Control ('N', CmdHistoryNext),
+ Handler.Control ('K', CmdKillToEOF),
+ Handler.Control ('Y', CmdYank),
+ Handler.Control ('D', CmdDeleteChar),
+ Handler.Control ('L', CmdRefresh),
+ Handler.Control ('R', CmdReverseSearch),
+ Handler.Control ('G', delegate {} ),
+ Handler.Alt ('B', ConsoleKey.B, CmdBackwardWord),
+ Handler.Alt ('F', ConsoleKey.F, CmdForwardWord),
+
+ Handler.Alt ('D', ConsoleKey.D, CmdDeleteWord),
+ Handler.Alt ((char) 8, ConsoleKey.Backspace, CmdDeleteBackword),
+
+ // DEBUG
+ //Handler.Control ('T', CmdDebug),
+
+ // quote
+ Handler.Control ('Q', delegate { HandleChar (Console.ReadKey (true).KeyChar); })
+ };
+
+ rendered_text = new StringBuilder ();
+ text = new StringBuilder ();
+
+ history = new History (name, histsize);
+
+ //if (File.Exists ("log"))File.Delete ("log");
+ //log = File.CreateText ("log");
+ }
+
+ void CmdDebug ()
+ {
+ history.Dump ();
+ Console.WriteLine ();
+ Render ();
+ }
+
+ void Render ()
+ {
+ Console.Write (shown_prompt);
+ Console.Write (rendered_text);
+
+ int max = System.Math.Max (rendered_text.Length + shown_prompt.Length, max_rendered);
+
+ for (int i = rendered_text.Length + shown_prompt.Length; i < max_rendered; i++)
+ Console.Write (' ');
+ max_rendered = shown_prompt.Length + rendered_text.Length;
+
+ // Write one more to ensure that we always wrap around properly if we are at the
+ // end of a line.
+ Console.Write (' ');
+
+ UpdateHomeRow (max);
+ }
+
+ void UpdateHomeRow (int screenpos)
+ {
+ int lines = 1 + (screenpos / Console.WindowWidth);
+
+ home_row = Console.CursorTop - (lines - 1);
+ if (home_row < 0)
+ home_row = 0;
+ }
+
+
+ void RenderFrom (int pos)
+ {
+ int rpos = TextToRenderPos (pos);
+ int i;
+
+ for (i = rpos; i < rendered_text.Length; i++)
+ Console.Write (rendered_text [i]);
+
+ if ((shown_prompt.Length + rendered_text.Length) > max_rendered)
+ max_rendered = shown_prompt.Length + rendered_text.Length;
+ else {
+ int max_extra = max_rendered - shown_prompt.Length;
+ for (; i < max_extra; i++)
+ Console.Write (' ');
+ }
+ }
+
+ void ComputeRendered ()
+ {
+ rendered_text.Length = 0;
+
+ for (int i = 0; i < text.Length; i++){
+ int c = (int) text [i];
+ if (c < 26){
+ if (c == '\t')
+ rendered_text.Append (" ");
+ else {
+ rendered_text.Append ('^');
+ rendered_text.Append ((char) (c + (int) 'A' - 1));
+ }
+ } else
+ rendered_text.Append ((char)c);
+ }
+ }
+
+ int TextToRenderPos (int pos)
+ {
+ int p = 0;
+
+ for (int i = 0; i < pos; i++){
+ int c;
+
+ c = (int) text [i];
+
+ if (c < 26){
+ if (c == 9)
+ p += 4;
+ else
+ p += 2;
+ } else
+ p++;
+ }
+
+ return p;
+ }
+
+ int TextToScreenPos (int pos)
+ {
+ return shown_prompt.Length + TextToRenderPos (pos);
+ }
+
+ string Prompt {
+ get { return prompt; }
+ set { prompt = value; }
+ }
+
+ int LineCount {
+ get {
+ return (shown_prompt.Length + rendered_text.Length)/Console.WindowWidth;
+ }
+ }
+
+ void ForceCursor (int newpos)
+ {
+ cursor = newpos;
+
+ int actual_pos = shown_prompt.Length + TextToRenderPos (cursor);
+ int row = home_row + (actual_pos/Console.WindowWidth);
+ int col = actual_pos % Console.WindowWidth;
+
+ if (row >= Console.BufferHeight)
+ row = Console.BufferHeight-1;
+ Console.SetCursorPosition (col, row);
+
+ //log.WriteLine ("Going to cursor={0} row={1} col={2} actual={3} prompt={4} ttr={5} old={6}", newpos, row, col, actual_pos, prompt.Length, TextToRenderPos (cursor), cursor);
+ //log.Flush ();
+ }
+
+ void UpdateCursor (int newpos)
+ {
+ if (cursor == newpos)
+ return;
+
+ ForceCursor (newpos);
+ }
+
+ void InsertChar (char c)
+ {
+ int prev_lines = LineCount;
+ text = text.Insert (cursor, c);
+ ComputeRendered ();
+ if (prev_lines != LineCount){
+
+ Console.SetCursorPosition (0, home_row);
+ Render ();
+ ForceCursor (++cursor);
+ } else {
+ RenderFrom (cursor);
+ ForceCursor (++cursor);
+ UpdateHomeRow (TextToScreenPos (cursor));
+ }
+ }
+
+ //
+ // Commands
+ //
+ void CmdDone ()
+ {
+ done = true;
+ }
+
+ void CmdTabOrComplete ()
+ {
+ bool complete = false;
+
+ if (AutoCompleteEvent != null){
+ if (TabAtStartCompletes)
+ complete = true;
+ else {
+ for (int i = 0; i < cursor; i++){
+ if (!Char.IsWhiteSpace (text [i])){
+ complete = true;
+ break;
+ }
+ }
+ }
+
+ if (complete){
+ Completion completion = AutoCompleteEvent (text.ToString (), cursor);
+ string [] completions = completion.Result;
+ if (completions == null)
+ return;
+
+ int ncompletions = completions.Length;
+ if (ncompletions == 0)
+ return;
+
+ if (completions.Length == 1){
+ InsertTextAtCursor (completions [0]);
+ } else {
+ int last = -1;
+
+ for (int p = 0; p < completions [0].Length; p++){
+ char c = completions [0][p];
+
+
+ for (int i = 1; i < ncompletions; i++){
+ if (completions [i].Length < p)
+ goto mismatch;
+
+ if (completions [i][p] != c){
+ goto mismatch;
+ }
+ }
+ last = p;
+ }
+ mismatch:
+ if (last != -1){
+ InsertTextAtCursor (completions [0].Substring (0, last+1));
+ }
+ Console.WriteLine ();
+ foreach (string s in completions){
+ Console.Write (completion.Prefix);
+ Console.Write (s);
+ Console.Write (' ');
+ }
+ Console.WriteLine ();
+ Render ();
+ ForceCursor (cursor);
+ }
+ } else
+ HandleChar ('\t');
+ } else
+ HandleChar ('t');
+ }
+
+ void CmdHome ()
+ {
+ UpdateCursor (0);
+ }
+
+ void CmdEnd ()
+ {
+ UpdateCursor (text.Length);
+ }
+
+ void CmdLeft ()
+ {
+ if (cursor == 0)
+ return;
+
+ UpdateCursor (cursor-1);
+ }
+
+ void CmdBackwardWord ()
+ {
+ int p = WordBackward (cursor);
+ if (p == -1)
+ return;
+ UpdateCursor (p);
+ }
+
+ void CmdForwardWord ()
+ {
+ int p = WordForward (cursor);
+ if (p == -1)
+ return;
+ UpdateCursor (p);
+ }
+
+ void CmdRight ()
+ {
+ if (cursor == text.Length)
+ return;
+
+ UpdateCursor (cursor+1);
+ }
+
+ void RenderAfter (int p)
+ {
+ ForceCursor (p);
+ RenderFrom (p);
+ ForceCursor (cursor);
+ }
+
+ void CmdBackspace ()
+ {
+ if (cursor == 0)
+ return;
+
+ text.Remove (--cursor, 1);
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ void CmdDeleteChar ()
+ {
+ // If there is no input, this behaves like EOF
+ if (text.Length == 0){
+ done = true;
+ text = null;
+ Console.WriteLine ();
+ return;
+ }
+
+ if (cursor == text.Length)
+ return;
+ text.Remove (cursor, 1);
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ int WordForward (int p)
+ {
+ if (p >= text.Length)
+ return -1;
+
+ int i = p;
+ if (Char.IsPunctuation (text [p]) || Char.IsSymbol (text [p]) || Char.IsWhiteSpace (text[p])){
+ for (; i < text.Length; i++){
+ if (Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ for (; i < text.Length; i++){
+ if (!Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ } else {
+ for (; i < text.Length; i++){
+ if (!Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ }
+ if (i != p)
+ return i;
+ return -1;
+ }
+
+ int WordBackward (int p)
+ {
+ if (p == 0)
+ return -1;
+
+ int i = p-1;
+ if (i == 0)
+ return 0;
+
+ if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text[i])){
+ for (; i >= 0; i--){
+ if (Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ for (; i >= 0; i--){
+ if (!Char.IsLetterOrDigit (text[i]))
+ break;
+ }
+ } else {
+ for (; i >= 0; i--){
+ if (!Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ }
+ i++;
+
+ if (i != p)
+ return i;
+
+ return -1;
+ }
+
+ void CmdDeleteWord ()
+ {
+ int pos = WordForward (cursor);
+
+ if (pos == -1)
+ return;
+
+ string k = text.ToString (cursor, pos-cursor);
+
+ if (last_handler == CmdDeleteWord)
+ kill_buffer = kill_buffer + k;
+ else
+ kill_buffer = k;
+
+ text.Remove (cursor, pos-cursor);
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ void CmdDeleteBackword ()
+ {
+ int pos = WordBackward (cursor);
+ if (pos == -1)
+ return;
+
+ string k = text.ToString (pos, cursor-pos);
+
+ if (last_handler == CmdDeleteBackword)
+ kill_buffer = k + kill_buffer;
+ else
+ kill_buffer = k;
+
+ text.Remove (pos, cursor-pos);
+ ComputeRendered ();
+ RenderAfter (pos);
+ }
+
+ //
+ // Adds the current line to the history if needed
+ //
+ void HistoryUpdateLine ()
+ {
+ history.Update (text.ToString ());
+ }
+
+ void CmdHistoryPrev ()
+ {
+ if (!history.PreviousAvailable ())
+ return;
+
+ HistoryUpdateLine ();
+
+ SetText (history.Previous ());
+ }
+
+ void CmdHistoryNext ()
+ {
+ if (!history.NextAvailable())
+ return;
+
+ history.Update (text.ToString ());
+ SetText (history.Next ());
+
+ }
+
+ void CmdKillToEOF ()
+ {
+ kill_buffer = text.ToString (cursor, text.Length-cursor);
+ text.Length = cursor;
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ void CmdYank ()
+ {
+ InsertTextAtCursor (kill_buffer);
+ }
+
+ void InsertTextAtCursor (string str)
+ {
+ int prev_lines = LineCount;
+ text.Insert (cursor, str);
+ ComputeRendered ();
+ if (prev_lines != LineCount){
+ Console.SetCursorPosition (0, home_row);
+ Render ();
+ cursor += str.Length;
+ ForceCursor (cursor);
+ } else {
+ RenderFrom (cursor);
+ cursor += str.Length;
+ ForceCursor (cursor);
+ UpdateHomeRow (TextToScreenPos (cursor));
+ }
+ }
+
+ void SetSearchPrompt (string s)
+ {
+ SetPrompt ("(reverse-i-search)`" + s + "': ");
+ }
+
+ void ReverseSearch ()
+ {
+ int p;
+
+ if (cursor == text.Length){
+ // The cursor is at the end of the string
+
+ p = text.ToString ().LastIndexOf (search);
+ if (p != -1){
+ match_at = p;
+ cursor = p;
+ ForceCursor (cursor);
+ return;
+ }
+ } else {
+ // The cursor is somewhere in the middle of the string
+ int start = (cursor == match_at) ? cursor - 1 : cursor;
+ if (start != -1){
+ p = text.ToString ().LastIndexOf (search, start);
+ if (p != -1){
+ match_at = p;
+ cursor = p;
+ ForceCursor (cursor);
+ return;
+ }
+ }
+ }
+
+ // Need to search backwards in history
+ HistoryUpdateLine ();
+ string s = history.SearchBackward (search);
+ if (s != null){
+ match_at = -1;
+ SetText (s);
+ ReverseSearch ();
+ }
+ }
+
+ void CmdReverseSearch ()
+ {
+ if (searching == 0){
+ match_at = -1;
+ last_search = search;
+ searching = -1;
+ search = "";
+ SetSearchPrompt ("");
+ } else {
+ if (search == ""){
+ if (last_search != "" && last_search != null){
+ search = last_search;
+ SetSearchPrompt (search);
+
+ ReverseSearch ();
+ }
+ return;
+ }
+ ReverseSearch ();
+ }
+ }
+
+ void SearchAppend (char c)
+ {
+ search = search + c;
+ SetSearchPrompt (search);
+
+ //
+ // If the new typed data still matches the current text, stay here
+ //
+ if (cursor < text.Length){
+ string r = text.ToString (cursor, text.Length - cursor);
+ if (r.StartsWith (search))
+ return;
+ }
+
+ ReverseSearch ();
+ }
+
+ void CmdRefresh ()
+ {
+ Console.Clear ();
+ max_rendered = 0;
+ Render ();
+ ForceCursor (cursor);
+ }
+
+ void InterruptEdit (object sender, ConsoleCancelEventArgs a)
+ {
+ // Do not abort our program:
+ a.Cancel = true;
+
+ // Interrupt the editor
+ edit_thread.Abort();
+ }
+
+ void HandleChar (char c)
+ {
+ if (searching != 0)
+ SearchAppend (c);
+ else
+ InsertChar (c);
+ }
+
+ void EditLoop ()
+ {
+ ConsoleKeyInfo cki;
+
+ while (!done){
+ ConsoleModifiers mod;
+
+ cki = Console.ReadKey (true);
+ if (cki.Key == ConsoleKey.Escape){
+ cki = Console.ReadKey (true);
+
+ mod = ConsoleModifiers.Alt;
+ } else
+ mod = cki.Modifiers;
+
+ bool handled = false;
+
+ foreach (Handler handler in handlers){
+ ConsoleKeyInfo t = handler.CKI;
+
+ if (t.Key == cki.Key && t.Modifiers == mod){
+ handled = true;
+ handler.KeyHandler ();
+ last_handler = handler.KeyHandler;
+ break;
+ } else if (t.KeyChar == cki.KeyChar && t.Key == ConsoleKey.Zoom){
+ handled = true;
+ handler.KeyHandler ();
+ last_handler = handler.KeyHandler;
+ break;
+ }
+ }
+ if (handled){
+ if (searching != 0){
+ if (last_handler != CmdReverseSearch){
+ searching = 0;
+ SetPrompt (prompt);
+ }
+ }
+ continue;
+ }
+
+ if (cki.KeyChar != (char) 0)
+ HandleChar (cki.KeyChar);
+ }
+ }
+
+ void InitText (string initial)
+ {
+ text = new StringBuilder (initial);
+ ComputeRendered ();
+ cursor = text.Length;
+ Render ();
+ ForceCursor (cursor);
+ }
+
+ void SetText (string newtext)
+ {
+ Console.SetCursorPosition (0, home_row);
+ InitText (newtext);
+ }
+
+ void SetPrompt (string newprompt)
+ {
+ shown_prompt = newprompt;
+ Console.SetCursorPosition (0, home_row);
+ Render ();
+ ForceCursor (cursor);
+ }
+
+ public string Edit (string prompt, string initial)
+ {
+ edit_thread = Thread.CurrentThread;
+ searching = 0;
+ Console.CancelKeyPress += InterruptEdit;
+
+ done = false;
+ history.CursorToEnd ();
+ max_rendered = 0;
+
+ Prompt = prompt;
+ shown_prompt = prompt;
+ InitText (initial);
+ history.Append (initial);
+
+ do {
+ try {
+ EditLoop ();
+ } catch (ThreadAbortException){
+ searching = 0;
+ Thread.ResetAbort ();
+ Console.WriteLine ();
+ SetPrompt (prompt);
+ SetText ("");
+ }
+ } while (!done);
+ Console.WriteLine ();
+
+ Console.CancelKeyPress -= InterruptEdit;
+
+ if (text == null){
+ history.Close ();
+ return null;
+ }
+
+ string result = text.ToString ();
+ if (result != "")
+ history.Accept (result);
+ else
+ history.RemoveLast ();
+
+ return result;
+ }
+
+ public void SaveHistory ()
+ {
+ if (history != null) {
+ history.Close ();
+ }
+ }
+
+ public bool TabAtStartCompletes { get; set; }
+
+ //
+ // Emulates the bash-like behavior, where edits done to the
+ // history are recorded
+ //
+ class History {
+ string [] history;
+ int head, tail;
+ int cursor, count;
+ string histfile;
+
+ public History (string app, int size)
+ {
+ if (size < 1)
+ throw new ArgumentException ("size");
+
+ if (app != null){
+ string dir = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
+ //Console.WriteLine (dir);
+ /*
+ if (!Directory.Exists (dir)){
+ try {
+ Directory.CreateDirectory (dir);
+ } catch {
+ app = null;
+ }
+ }
+ if (app != null)
+ histfile = Path.Combine (dir, app) + ".history";
+ */
+ histfile = Path.Combine (dir, ".mal-history");
+ }
+
+ history = new string [size];
+ head = tail = cursor = 0;
+
+ if (File.Exists (histfile)){
+ using (StreamReader sr = File.OpenText (histfile)){
+ string line;
+
+ while ((line = sr.ReadLine ()) != null){
+ if (line != "")
+ Append (line);
+ }
+ }
+ }
+ }
+
+ public void Close ()
+ {
+ if (histfile == null)
+ return;
+
+ try {
+ using (StreamWriter sw = File.CreateText (histfile)){
+ int start = (count == history.Length) ? head : tail;
+ for (int i = start; i < start+count; i++){
+ int p = i % history.Length;
+ sw.WriteLine (history [p]);
+ }
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ //
+ // Appends a value to the history
+ //
+ public void Append (string s)
+ {
+ //Console.WriteLine ("APPENDING {0} head={1} tail={2}", s, head, tail);
+ history [head] = s;
+ head = (head+1) % history.Length;
+ if (head == tail)
+ tail = (tail+1 % history.Length);
+ if (count != history.Length)
+ count++;
+ //Console.WriteLine ("DONE: head={1} tail={2}", s, head, tail);
+ }
+
+ //
+ // Updates the current cursor location with the string,
+ // to support editing of history items. For the current
+ // line to participate, an Append must be done before.
+ //
+ public void Update (string s)
+ {
+ history [cursor] = s;
+ }
+
+ public void RemoveLast ()
+ {
+ head = head-1;
+ if (head < 0)
+ head = history.Length-1;
+ }
+
+ public void Accept (string s)
+ {
+ int t = head-1;
+ if (t < 0)
+ t = history.Length-1;
+
+ history [t] = s;
+ }
+
+ public bool PreviousAvailable ()
+ {
+ //Console.WriteLine ("h={0} t={1} cursor={2}", head, tail, cursor);
+ if (count == 0)
+ return false;
+ int next = cursor-1;
+ if (next < 0)
+ next = count-1;
+
+ if (next == head)
+ return false;
+
+ return true;
+ }
+
+ public bool NextAvailable ()
+ {
+ if (count == 0)
+ return false;
+ int next = (cursor + 1) % history.Length;
+ if (next == head)
+ return false;
+ return true;
+ }
+
+
+ //
+ // Returns: a string with the previous line contents, or
+ // nul if there is no data in the history to move to.
+ //
+ public string Previous ()
+ {
+ if (!PreviousAvailable ())
+ return null;
+
+ cursor--;
+ if (cursor < 0)
+ cursor = history.Length - 1;
+
+ return history [cursor];
+ }
+
+ public string Next ()
+ {
+ if (!NextAvailable ())
+ return null;
+
+ cursor = (cursor + 1) % history.Length;
+ return history [cursor];
+ }
+
+ public void CursorToEnd ()
+ {
+ if (head == tail)
+ return;
+
+ cursor = head;
+ }
+
+ public void Dump ()
+ {
+ Console.WriteLine ("Head={0} Tail={1} Cursor={2} count={3}", head, tail, cursor, count);
+ for (int i = 0; i < history.Length;i++){
+ Console.WriteLine (" {0} {1}: {2}", i == cursor ? "==>" : " ", i, history[i]);
+ }
+ //log.Flush ();
+ }
+
+ public string SearchBackward (string term)
+ {
+ for (int i = 0; i < count; i++){
+ int slot = cursor-i-1;
+ if (slot < 0)
+ slot = history.Length+slot;
+ if (slot >= history.Length)
+ slot = 0;
+ if (history [slot] != null && history [slot].IndexOf (term) != -1){
+ cursor = slot;
+ return history [slot];
+ }
+ }
+
+ return null;
+ }
+
+ }
+ }
+
+#if DEMO
+ class Demo {
+ static void Main ()
+ {
+ LineEditor le = new LineEditor ("foo");
+ string s;
+
+ while ((s = le.Edit ("shell> ", "")) != null){
+ Console.WriteLine ("----> [{0}]", s);
+ }
+ }
+ }
+#endif
+}
diff --git a/vb/printer.vb b/vb/printer.vb
new file mode 100644
index 0000000..3f3e6e2
--- /dev/null
+++ b/vb/printer.vb
@@ -0,0 +1,52 @@
+Imports System
+Imports System.Collections.Generic
+Imports System.Text.RegularExpressions
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalList = Mal.types.MalList
+
+Namespace Mal
+ Public Class printer
+ Shared Function join(value As List(Of MalVal),
+ delim As String,
+ print_readably As Boolean) As String
+ Dim strs As New List(Of String)
+ For Each mv As MalVal In value
+ strs.Add(mv.ToString(print_readably))
+ Next
+ return String.Join(delim, strs.ToArray())
+ End Function
+
+ Shared Function join(value As Dictionary(Of String, MalVal),
+ delim As String,
+ print_readably As Boolean) As String
+ Dim strs As New List(Of String)
+ For Each entry As KeyValuePair(Of String, MalVal) In value
+ If entry.Key.Length > 0 and entry.Key(0) = ChrW(&H029e) Then
+ strs.Add(":" & entry.Key.Substring(1))
+ Else If print_readably Then
+ strs.Add("""" & entry.Key.ToString() & """")
+ Else
+ strs.Add(entry.Key.ToString())
+ End If
+ strs.Add(entry.Value.ToString(print_readably))
+ Next
+ return String.Join(delim, strs.ToArray())
+ End Function
+
+ Shared Function _pr_str(mv As MalVal,
+ print_readably As Boolean) As String
+ return mv.ToString(print_readably)
+ End Function
+
+ Shared Function _pr_str_args(args As MalList,
+ sep As String,
+ print_readably As Boolean) As String
+ return join(args.getValue(), sep, print_readably)
+ End Function
+
+ Shared Function escapeString(str As String) As String
+ return Regex.Escape(str)
+ End Function
+ End Class
+End Namespace
diff --git a/vb/reader.vb b/vb/reader.vb
new file mode 100644
index 0000000..9d4e03d
--- /dev/null
+++ b/vb/reader.vb
@@ -0,0 +1,183 @@
+Imports System
+Imports System.Collections
+Imports System.Collections.Generic
+Imports System.Text.RegularExpressions
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalThrowable = Mal.types.MalThrowable
+Imports MalContinue = Mal.types.MalContinue
+
+Namespace Mal
+ Public Class reader
+ Public Class ParseError
+ Inherits MalThrowable
+ Public Sub New(msg As String)
+ MyBase.New(msg)
+ End Sub
+ End Class
+
+ Public Class Reader
+ Private tokens As New List(Of String)
+ Private position As Int32 = 0
+ Sub New(t As List(Of String))
+ tokens = t
+ position = 0
+ End Sub
+
+ Public Function peek() As String
+ If position >= tokens.Count Then
+ return Nothing
+ Else
+ return tokens(position)
+ End If
+ End Function
+
+ Public Function get_next() As String
+ If position >= tokens.Count Then
+ return Nothing
+ Else
+ position += 1
+ return tokens(position-1)
+ End If
+ End Function
+ End Class
+
+ Shared Function tokenize(str As String) As List(Of String)
+ Dim tokens As New List(Of String)
+ Dim pattern As String = "[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""|;.*|[^\s \[\]{}()'""`~@,;]*)"
+ Dim regex As New Regex(pattern)
+ For Each match As Match In regex.Matches(str)
+ Dim token As String = match.Groups(1).Value
+ If Not token Is Nothing _
+ AndAlso Not token = "" _
+ AndAlso Not token(0) = ";" Then
+ 'Console.WriteLine("match: ^" & match.Groups[1] & "$")
+ tokens.Add(token)
+ End If
+ Next
+ return tokens
+ End Function
+
+ Shared Function read_atom(rdr As Reader) As MalVal
+ Dim token As String = rdr.get_next()
+ Dim pattern As String = "(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|^:(.*)|(^[^""]*$)"
+ Dim regex As Regex = New Regex(pattern)
+ Dim match As Match = regex.Match(token)
+ 'Console.WriteLine("token: ^" + token + "$")
+ If not match.Success Then
+ throw New ParseError("unrecognized token '" & token & "'")
+ End If
+ If match.Groups(1).Value <> String.Empty Then
+ return New Mal.types.MalInt(Integer.Parse(match.Groups(1).Value))
+ Else If match.Groups(3).Value <> String.Empty Then
+ return Mal.types.Nil
+ Else If match.Groups(4).Value <> String.Empty Then
+ return Mal.types.MalTrue
+ Else If match.Groups(5).Value <> String.Empty Then
+ return Mal.types.MalFalse
+ Else If match.Groups(6).Value <> String.Empty Then
+ Dim str As String = match.Groups(6).Value
+ return New Mal.types.MalString(
+ str.Substring(1, str.Length-2) _
+ .Replace("\""", """") _
+ .Replace("\n", Environment.NewLine))
+ Else If match.Groups(7).Value <> String.Empty Then
+ return New Mal.types.MalString(ChrW(&H029e) & match.Groups(7).Value)
+ Else If match.Groups(8).Value <> String.Empty Then
+ return New Mal.types.MalSymbol(match.Groups(8).Value)
+ Else
+ throw New ParseError("unrecognized '" & match.Groups(0).Value & "'")
+ End If
+ End Function
+
+ Shared Function read_list(rdr As Reader, lst As MalList,
+ start As String, last As String) As MalVal
+ Dim token As String = rdr.get_next()
+ If token(0) <> start Then
+ throw New ParseError("expected '" & start & "'")
+ End If
+
+ token = rdr.peek()
+ While token IsNot Nothing AndAlso token(0) <> last
+ lst.conj_BANG(read_form(rdr))
+ token = rdr.peek()
+ End While
+
+ If token Is Nothing Then
+ throw New ParseError("expected '" & last & "', got EOF")
+ End If
+ rdr.get_next()
+
+ return lst
+ End Function
+
+ Shared Function read_hash_map(rdr As Reader) As MalVal
+ Dim lst As MalList = DirectCast(read_list(rdr, new MalList(),
+ "{", "}"),MalList)
+ return New MalHashMap(lst)
+ End Function
+
+
+ Shared Function read_form(rdr As Reader) As MalVal
+ Dim token As String = rdr.peek()
+ If token Is Nothing Then
+ throw New MalContinue()
+ End If
+ Dim form As MalVal = Nothing
+
+ Select token
+ Case "'"
+ rdr.get_next()
+ return New MalList(New MalSymbol("quote"),
+ read_form(rdr))
+ Case "`"
+ rdr.get_next()
+ return New MalList(New MalSymbol("quasiquote"),
+ read_form(rdr))
+ Case "~"
+ rdr.get_next()
+ return New MalList(New MalSymbol("unquote"),
+ read_form(rdr))
+ Case "~@"
+ rdr.get_next()
+ return new MalList(New MalSymbol("splice-unquote"),
+ read_form(rdr))
+ Case "^"
+ rdr.get_next()
+ Dim meta As MalVal = read_form(rdr)
+ return new MalList(New MalSymbol("with-meta"),
+ read_form(rdr),
+ meta)
+ Case "@"
+ rdr.get_next()
+ return new MalList(New MalSymbol("deref"),
+ read_form(rdr))
+
+ Case "("
+ form = read_list(rdr, New MalList(), "(" , ")")
+ Case ")"
+ throw New ParseError("unexpected ')'")
+ Case "["
+ form = read_list(rdr, New MalVector(), "[" , "]")
+ Case "]"
+ throw New ParseError("unexpected ']'")
+ Case "{"
+ form = read_hash_map(rdr)
+ Case "}"
+ throw New ParseError("unexpected '}'")
+ Case Else
+ form = read_atom(rdr)
+ End Select
+ return form
+ End Function
+
+
+ Shared Function read_str(str As string) As MalVal
+ return read_form(New Reader(tokenize(str)))
+ End Function
+ End Class
+End Namespace
diff --git a/vb/readline.vb b/vb/readline.vb
new file mode 100644
index 0000000..74047ce
--- /dev/null
+++ b/vb/readline.vb
@@ -0,0 +1,32 @@
+Imports System
+Imports Mono.Terminal ' LineEditor (getline.cs)
+
+Namespace Mal
+ Public Class readline
+ Enum Modes
+ Terminal
+ Raw
+ End Enum
+
+ Public Shared mode As Modes = Modes.Terminal
+
+ Shared lineedit As LineEditor = Nothing
+
+ Public Shared Sub SetMode(new_mode As Modes)
+ mode = new_mode
+ End Sub
+
+ Public Shared Function Readline(prompt As String) As String
+ If mode = Modes.Terminal Then
+ If lineedit Is Nothing Then
+ lineedit = New LineEditor("Mal")
+ End If
+ return lineedit.Edit(prompt, "")
+ Else
+ Console.Write(prompt)
+ Console.Out.Flush()
+ return Console.ReadLine()
+ End If
+ End Function
+ End Class
+End Namespace
diff --git a/vb/step0_repl.vb b/vb/step0_repl.vb
new file mode 100644
index 0000000..f53f378
--- /dev/null
+++ b/vb/step0_repl.vb
@@ -0,0 +1,43 @@
+Imports System
+Imports Mal
+
+Namespace Mal
+ Class step0_repl
+ ' read
+ Shared Function READ(str As String) As String
+ Return str
+ End Function
+
+ ' eval
+ Shared Function EVAL(ast As String, env As String) As String
+ Return ast
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As String) As String
+ Return exp
+ End Function
+
+ ' repl
+ Shared Function REP(str As String, env As String) As String
+ Return PRINT(EVAL(READ(str), env))
+ End Function
+
+ Shared Function Main As Integer
+ Dim prompt As String = "user> "
+ Dim line As String
+
+ Do
+ line = Mal.readline.Readline(prompt)
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Console.WriteLine(REP(line, ""))
+ Loop While True
+ Return 0
+ End function
+ End Class
+End Namespace
diff --git a/vb/step1_read_print.vb b/vb/step1_read_print.vb
new file mode 100644
index 0000000..2734973
--- /dev/null
+++ b/vb/step1_read_print.vb
@@ -0,0 +1,59 @@
+Imports System
+Imports System.IO
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+
+Namespace Mal
+ Class step1_read_print
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function EVAL(ast As MalVal, env As String) As MalVal
+ Return ast
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), ""))
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step2_eval.vb b/vb/step2_eval.vb
new file mode 100644
index 0000000..6e45efe
--- /dev/null
+++ b/vb/step2_eval.vb
@@ -0,0 +1,134 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+
+Namespace Mal
+ Class step2_eval
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function eval_ast(ast As MalVal, env As Dictionary(Of String, MalVal)) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ Dim sym As MalSymbol = DirectCast(ast, MalSymbol)
+ return env.Item(sym.getName())
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ Shared Function EVAL(orig_ast As MalVal, env As Dictionary(Of String, MalVal)) As MalVal
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim ast As MalList = DirectCast(orig_ast, MalList)
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Return f.apply(el.rest())
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As Dictionary(Of String, MalVal)
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function add(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) + DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function minus(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) - DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function mult(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) * DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function div(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) / DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New Dictionary(Of String, MalVal)
+ repl_env.Add("+", New MalFunc(AddressOf add))
+ repl_env.Add("-", New MalFunc(AddressOf minus))
+ repl_env.Add("*", New MalFunc(AddressOf mult))
+ repl_env.Add("/", New MalFunc(AddressOf div))
+
+
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step3_env.vb b/vb/step3_env.vb
new file mode 100644
index 0000000..dfee614
--- /dev/null
+++ b/vb/step3_env.vb
@@ -0,0 +1,155 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step3_env
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim ast As MalList = DirectCast(orig_ast, MalList)
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Select DirectCast(a0,MalSymbol).getName()
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ return EVAL(a2, let_env)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Return f.apply(el.rest())
+ End Select
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function add(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) + DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function minus(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) - DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function mult(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) * DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function div(a As MalList) As MalVal
+ Return DirectCast(a.Item(0),MalInt) / DirectCast(a.Item(1),MalInt)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+ repl_env.do_set(new MalSymbol("+"), New MalFunc(AddressOf add))
+ repl_env.do_set(new MalSymbol("-"), New MalFunc(AddressOf minus))
+ repl_env.do_set(new MalSymbol("*"), New MalFunc(AddressOf mult))
+ repl_env.do_set(new MalSymbol("/"), New MalFunc(AddressOf div))
+
+
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step4_if_fn_do.vb b/vb/step4_if_fn_do.vb
new file mode 100644
index 0000000..470ae86
--- /dev/null
+++ b/vb/step4_if_fn_do.vb
@@ -0,0 +1,188 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step4_if_fn_do
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim ast As MalList = DirectCast(orig_ast, MalList)
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ return EVAL(a2, let_env)
+ Case "do"
+ Dim el As MalList = DirectCast(eval_ast(ast.rest(), env), _
+ MalLIst)
+ return el(el.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ Dim a3 As MalVal = ast(3)
+ return EVAL(a3, env)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ Dim a2 As MalVal = ast(2)
+ return EVAL(a2, env)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Return f.apply(el.rest())
+ End Select
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+
+ ' core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))")
+
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step5_tco.vb b/vb/step5_tco.vb
new file mode 100644
index 0000000..bb36b22
--- /dev/null
+++ b/vb/step5_tco.vb
@@ -0,0 +1,197 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step5_tco
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ Do
+
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim ast As MalList = DirectCast(orig_ast, MalList)
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ orig_ast = a2
+ env = let_env
+ Case "do"
+ eval_ast(ast.slice(1, ast.size()-1), env)
+ orig_ast = ast(ast.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ orig_ast = ast(3)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ orig_ast = ast(2)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(ast(2), env,
+ DirectCast(ast(1),MalList), f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Dim fnast As MalVal = f.getAst()
+ If not fnast Is Nothing
+ orig_ast = fnast
+ env = f.genEnv(el.rest())
+ Else
+ Return f.apply(el.rest())
+ End If
+ End Select
+
+ Loop While True
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+
+ ' core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))")
+
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step6_file.vb b/vb/step6_file.vb
new file mode 100644
index 0000000..9ea0e9f
--- /dev/null
+++ b/vb/step6_file.vb
@@ -0,0 +1,215 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalString = Mal.types.MalString
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step6_file
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ Do
+
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim ast As MalList = DirectCast(orig_ast, MalList)
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ orig_ast = a2
+ env = let_env
+ Case "do"
+ eval_ast(ast.slice(1, ast.size()-1), env)
+ orig_ast = ast(ast.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ orig_ast = ast(3)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ orig_ast = ast(2)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(ast(2), env,
+ DirectCast(ast(1),MalList), f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Dim fnast As MalVal = f.getAst()
+ If not fnast Is Nothing
+ orig_ast = fnast
+ env = f.genEnv(el.rest())
+ Else
+ Return f.apply(el.rest())
+ End If
+ End Select
+
+ Loop While True
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function do_eval(args As MalList) As MalVal
+ Return EVAL(args(0), repl_env)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+ repl_env.do_set(new MalSymbol("eval"), new MalFunc(AddressOf do_eval))
+ Dim fileIdx As Integer = 1
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ fileIdx = 2
+ End If
+ Dim argv As New MalList()
+ For i As Integer = fileIdx+1 To args.Length-1
+ argv.conj_BANG(new MalString(args(i)))
+ Next
+ repl_env.do_set(new MalSymbol("*ARGV*"), argv)
+
+ ' 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 args.Length > fileIdx Then
+ REP("(load-file """ & args(fileIdx) & """)")
+ return 0
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step7_quote.vb b/vb/step7_quote.vb
new file mode 100644
index 0000000..f38741e
--- /dev/null
+++ b/vb/step7_quote.vb
@@ -0,0 +1,248 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalString = Mal.types.MalString
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step7_quote
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function is_pair(x As MalVal) As Boolean
+ return TypeOf x Is MalList AndAlso _
+ DirectCast(x,MalList).size() > 0
+ End Function
+
+ Shared Function quasiquote(ast As MalVal) As MalVal
+ If not is_pair(ast) Then
+ return New MalList(New MalSymbol("quote"), ast)
+ Else
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ DirectCast(a0,MalSymbol).getName() = "unquote" Then
+ return DirectCast(ast,MalList)(1)
+ Else If is_pair(a0) Then
+ Dim a00 As MalVal = DirectCast(a0,MalList)(0)
+ If TypeOf a00 is MalSymbol AndAlso _
+ DirectCast(a00,MalSymbol).getName() = "splice-unquote" Then
+ return New MalList(New MalSymbol("concat"),
+ DirectCast(a0,MalList)(1),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End If
+ return New MalList(New MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End Function
+
+
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ Do
+
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim ast As MalList = DirectCast(orig_ast, MalList)
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ orig_ast = a2
+ env = let_env
+ Case "quote"
+ return ast(1)
+ Case "quasiquote"
+ orig_ast = quasiquote(ast(1))
+ Case "do"
+ eval_ast(ast.slice(1, ast.size()-1), env)
+ orig_ast = ast(ast.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ orig_ast = ast(3)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ orig_ast = ast(2)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(ast(2), env,
+ DirectCast(ast(1),MalList), f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Dim fnast As MalVal = f.getAst()
+ If not fnast Is Nothing
+ orig_ast = fnast
+ env = f.genEnv(el.rest())
+ Else
+ Return f.apply(el.rest())
+ End If
+ End Select
+
+ Loop While True
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function do_eval(args As MalList) As MalVal
+ Return EVAL(args(0), repl_env)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+ repl_env.do_set(new MalSymbol("eval"), new MalFunc(AddressOf do_eval))
+ Dim fileIdx As Integer = 1
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ fileIdx = 2
+ End If
+ Dim argv As New MalList()
+ For i As Integer = fileIdx+1 To args.Length-1
+ argv.conj_BANG(new MalString(args(i)))
+ Next
+ repl_env.do_set(new MalSymbol("*ARGV*"), argv)
+
+ ' 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 args.Length > fileIdx Then
+ REP("(load-file """ & args(fileIdx) & """)")
+ return 0
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step8_macros.vb b/vb/step8_macros.vb
new file mode 100644
index 0000000..400dbe7
--- /dev/null
+++ b/vb/step8_macros.vb
@@ -0,0 +1,288 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalString = Mal.types.MalString
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step8_macros
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function is_pair(x As MalVal) As Boolean
+ return TypeOf x Is MalList AndAlso _
+ DirectCast(x,MalList).size() > 0
+ End Function
+
+ Shared Function quasiquote(ast As MalVal) As MalVal
+ If not is_pair(ast) Then
+ return New MalList(New MalSymbol("quote"), ast)
+ Else
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ DirectCast(a0,MalSymbol).getName() = "unquote" Then
+ return DirectCast(ast,MalList)(1)
+ Else If is_pair(a0) Then
+ Dim a00 As MalVal = DirectCast(a0,MalList)(0)
+ If TypeOf a00 is MalSymbol AndAlso _
+ DirectCast(a00,MalSymbol).getName() = "splice-unquote" Then
+ return New MalList(New MalSymbol("concat"),
+ DirectCast(a0,MalList)(1),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End If
+ return New MalList(New MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End Function
+
+ Shared Function is_macro_call(ast As MalVal, env As MalEnv) As Boolean
+ If TypeOf ast Is MalList Then
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ env.find(DirectCast(a0,MalSymbol)) IsNot Nothing Then
+ Dim mac As MalVal = env.do_get(DirectCast(a0,MalSymbol))
+ If TypeOf mac Is MalFunc AndAlso _
+ DirectCast(mac,MalFunc).isMacro() Then
+ return True
+ End If
+ End If
+ End If
+ return False
+ End Function
+
+ Shared Function macroexpand(ast As MalVal, env As MalEnv) As MalVal
+ While is_macro_call(ast, env)
+ Dim a0 As MalSymbol = DirectCast(DirectCast(ast,MalList)(0),MalSymbol)
+ Dim mac As MalFunc = DirectCast(env.do_get(a0),MalFunc)
+ ast = mac.apply(DirectCast(ast,MalList).rest())
+ End While
+ return ast
+ End Function
+
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ Do
+
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim expanded As MalVal = macroexpand(orig_ast, env)
+ if not expanded.list_Q() Then
+ return expanded
+ End If
+ Dim ast As MalList = DirectCast(expanded, MalList)
+
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ orig_ast = a2
+ env = let_env
+ Case "quote"
+ return ast(1)
+ Case "quasiquote"
+ orig_ast = quasiquote(ast(1))
+ Case "defmacro!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ DirectCast(res,MalFunc).setMacro()
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "macroexpand"
+ Dim a1 As MalVal = ast(1)
+ return macroexpand(a1, env)
+ Case "do"
+ eval_ast(ast.slice(1, ast.size()-1), env)
+ orig_ast = ast(ast.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ orig_ast = ast(3)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ orig_ast = ast(2)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(ast(2), env,
+ DirectCast(ast(1),MalList), f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Dim fnast As MalVal = f.getAst()
+ If not fnast Is Nothing
+ orig_ast = fnast
+ env = f.genEnv(el.rest())
+ Else
+ Return f.apply(el.rest())
+ End If
+ End Select
+
+ Loop While True
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function do_eval(args As MalList) As MalVal
+ Return EVAL(args(0), repl_env)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+ repl_env.do_set(new MalSymbol("eval"), new MalFunc(AddressOf do_eval))
+ Dim fileIdx As Integer = 1
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ fileIdx = 2
+ End If
+ Dim argv As New MalList()
+ For i As Integer = fileIdx+1 To args.Length-1
+ argv.conj_BANG(new MalString(args(i)))
+ Next
+ repl_env.do_set(new MalSymbol("*ARGV*"), argv)
+
+ ' 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 args.Length > fileIdx Then
+ REP("(load-file """ & args(fileIdx) & """)")
+ return 0
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e as Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/step9_try.vb b/vb/step9_try.vb
new file mode 100644
index 0000000..d4a7af4
--- /dev/null
+++ b/vb/step9_try.vb
@@ -0,0 +1,315 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalString = Mal.types.MalString
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class step9_try
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function is_pair(x As MalVal) As Boolean
+ return TypeOf x Is MalList AndAlso _
+ DirectCast(x,MalList).size() > 0
+ End Function
+
+ Shared Function quasiquote(ast As MalVal) As MalVal
+ If not is_pair(ast) Then
+ return New MalList(New MalSymbol("quote"), ast)
+ Else
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ DirectCast(a0,MalSymbol).getName() = "unquote" Then
+ return DirectCast(ast,MalList)(1)
+ Else If is_pair(a0) Then
+ Dim a00 As MalVal = DirectCast(a0,MalList)(0)
+ If TypeOf a00 is MalSymbol AndAlso _
+ DirectCast(a00,MalSymbol).getName() = "splice-unquote" Then
+ return New MalList(New MalSymbol("concat"),
+ DirectCast(a0,MalList)(1),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End If
+ return New MalList(New MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End Function
+
+ Shared Function is_macro_call(ast As MalVal, env As MalEnv) As Boolean
+ If TypeOf ast Is MalList Then
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ env.find(DirectCast(a0,MalSymbol)) IsNot Nothing Then
+ Dim mac As MalVal = env.do_get(DirectCast(a0,MalSymbol))
+ If TypeOf mac Is MalFunc AndAlso _
+ DirectCast(mac,MalFunc).isMacro() Then
+ return True
+ End If
+ End If
+ End If
+ return False
+ End Function
+
+ Shared Function macroexpand(ast As MalVal, env As MalEnv) As MalVal
+ While is_macro_call(ast, env)
+ Dim a0 As MalSymbol = DirectCast(DirectCast(ast,MalList)(0),MalSymbol)
+ Dim mac As MalFunc = DirectCast(env.do_get(a0),MalFunc)
+ ast = mac.apply(DirectCast(ast,MalList).rest())
+ End While
+ return ast
+ End Function
+
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ Do
+
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim expanded As MalVal = macroexpand(orig_ast, env)
+ if not expanded.list_Q() Then
+ return expanded
+ End If
+ Dim ast As MalList = DirectCast(expanded, MalList)
+
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ orig_ast = a2
+ env = let_env
+ Case "quote"
+ return ast(1)
+ Case "quasiquote"
+ orig_ast = quasiquote(ast(1))
+ Case "defmacro!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ DirectCast(res,MalFunc).setMacro()
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "macroexpand"
+ Dim a1 As MalVal = ast(1)
+ return macroexpand(a1, env)
+ Case "try*"
+ Try
+ return EVAL(ast(1), env)
+ Catch e As Exception
+ If ast.size() > 2 Then
+ Dim exc As MalVal
+ Dim a2 As MalVal = ast(2)
+ Dim a20 As MalVal = DirectCast(a2,MalList)(0)
+ If DirectCast(a20,MalSymbol).getName() = "catch*" Then
+ If TypeOf e Is Mal.types.MalException Then
+ exc = DirectCast(e,Mal.types.MalException).getValue()
+ Else
+ exc = New MalString(e.StackTrace)
+ End If
+ return EVAL(
+ DirectCast(a2,MalList)(2),
+ New MalEnv(env,
+ DirectCast(a2,MalList).slice(1,2),
+ New MalList(exc)))
+ End If
+ Throw e
+ End If
+ End Try
+ Case "do"
+ eval_ast(ast.slice(1, ast.size()-1), env)
+ orig_ast = ast(ast.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ orig_ast = ast(3)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ orig_ast = ast(2)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(ast(2), env,
+ DirectCast(ast(1),MalList), f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Dim fnast As MalVal = f.getAst()
+ If not fnast Is Nothing
+ orig_ast = fnast
+ env = f.genEnv(el.rest())
+ Else
+ Return f.apply(el.rest())
+ End If
+ End Select
+
+ Loop While True
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function do_eval(args As MalList) As MalVal
+ Return EVAL(args(0), repl_env)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+ repl_env.do_set(new MalSymbol("eval"), new MalFunc(AddressOf do_eval))
+ Dim fileIdx As Integer = 1
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ fileIdx = 2
+ End If
+ Dim argv As New MalList()
+ For i As Integer = fileIdx+1 To args.Length-1
+ argv.conj_BANG(new MalString(args(i)))
+ Next
+ repl_env.do_set(new MalSymbol("*ARGV*"), argv)
+
+ ' 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 args.Length > fileIdx Then
+ REP("(load-file """ & args(fileIdx) & """)")
+ return 0
+ End If
+
+ ' repl loop
+ Dim line As String
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e As Mal.types.MalException
+ Console.WriteLine("Error: " & _
+ printer._pr_str(e.getValue(), False))
+ Continue Do
+ Catch e As Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/stepA_mal.vb b/vb/stepA_mal.vb
new file mode 100644
index 0000000..6778e0a
--- /dev/null
+++ b/vb/stepA_mal.vb
@@ -0,0 +1,317 @@
+Imports System
+Imports System.IO
+Imports System.Collections.Generic
+Imports Mal
+Imports MalVal = Mal.types.MalVal
+Imports MalInt = Mal.types.MalInt
+Imports MalString = Mal.types.MalString
+Imports MalSymbol = Mal.types.MalSymbol
+Imports MalList = Mal.types.MalList
+Imports MalVector = Mal.types.MalVector
+Imports MalHashMap = Mal.types.MalHashMap
+Imports MalFunc = Mal.types.MalFunc
+Imports MalEnv = Mal.env.Env
+
+Namespace Mal
+ Class stepA_mal
+ ' read
+ Shared Function READ(str As String) As MalVal
+ Return reader.read_str(str)
+ End Function
+
+ ' eval
+ Shared Function is_pair(x As MalVal) As Boolean
+ return TypeOf x Is MalList AndAlso _
+ DirectCast(x,MalList).size() > 0
+ End Function
+
+ Shared Function quasiquote(ast As MalVal) As MalVal
+ If not is_pair(ast) Then
+ return New MalList(New MalSymbol("quote"), ast)
+ Else
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ DirectCast(a0,MalSymbol).getName() = "unquote" Then
+ return DirectCast(ast,MalList)(1)
+ Else If is_pair(a0) Then
+ Dim a00 As MalVal = DirectCast(a0,MalList)(0)
+ If TypeOf a00 is MalSymbol AndAlso _
+ DirectCast(a00,MalSymbol).getName() = "splice-unquote" Then
+ return New MalList(New MalSymbol("concat"),
+ DirectCast(a0,MalList)(1),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End If
+ return New MalList(New MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(DirectCast(ast,MalList).rest()))
+ End If
+ End Function
+
+ Shared Function is_macro_call(ast As MalVal, env As MalEnv) As Boolean
+ If TypeOf ast Is MalList Then
+ Dim a0 As MalVal = DirectCast(ast,MalList)(0)
+ If TypeOf a0 Is MalSymbol AndAlso _
+ env.find(DirectCast(a0,MalSymbol)) IsNot Nothing Then
+ Dim mac As MalVal = env.do_get(DirectCast(a0,MalSymbol))
+ If TypeOf mac Is MalFunc AndAlso _
+ DirectCast(mac,MalFunc).isMacro() Then
+ return True
+ End If
+ End If
+ End If
+ return False
+ End Function
+
+ Shared Function macroexpand(ast As MalVal, env As MalEnv) As MalVal
+ While is_macro_call(ast, env)
+ Dim a0 As MalSymbol = DirectCast(DirectCast(ast,MalList)(0),MalSymbol)
+ Dim mac As MalFunc = DirectCast(env.do_get(a0),MalFunc)
+ ast = mac.apply(DirectCast(ast,MalList).rest())
+ End While
+ return ast
+ End Function
+
+ Shared Function eval_ast(ast As MalVal, env As MalEnv) As MalVal
+ If TypeOf ast Is MalSymbol Then
+ return env.do_get(DirectCast(ast, MalSymbol))
+ Else If TypeOf ast Is MalList Then
+ Dim old_lst As MalList = DirectCast(ast, MalList)
+ Dim new_lst As MalList
+ If ast.list_Q() Then
+ new_lst = New MalList
+ Else
+ new_lst = DirectCast(New MalVector, MalList)
+ End If
+ Dim mv As MalVal
+ For Each mv in old_lst.getValue()
+ new_lst.conj_BANG(EVAL(mv, env))
+ Next
+ return new_lst
+ Else If TypeOf ast Is MalHashMap Then
+ Dim new_dict As New Dictionary(Of String, MalVal)
+ Dim entry As KeyValuePair(Of String, MalVal)
+ For Each entry in DirectCast(ast,MalHashMap).getValue()
+ new_dict.Add(entry.Key, EVAL(DirectCast(entry.Value,MalVal), env))
+ Next
+ return New MalHashMap(new_dict)
+ Else
+ return ast
+ End If
+ return ast
+ End Function
+
+ ' TODO: move to types.vb when it is ported
+ Class FClosure
+ Public ast As MalVal
+ Public params As MalList
+ Public env As MalEnv
+ Function fn(args as MalList) As MalVal
+ return EVAL(ast, new MalEnv(env, params, args))
+ End Function
+ End Class
+
+ Shared Function EVAL(orig_ast As MalVal, env As MalEnv) As MalVal
+ Do
+
+ 'Console.WriteLine("EVAL: {0}", printer._pr_str(orig_ast, true))
+ If not orig_ast.list_Q() Then
+ return eval_ast(orig_ast, env)
+ End If
+
+ ' apply list
+ Dim expanded As MalVal = macroexpand(orig_ast, env)
+ if not expanded.list_Q() Then
+ return expanded
+ End If
+ Dim ast As MalList = DirectCast(expanded, MalList)
+
+ If ast.size() = 0 Then
+ return ast
+ End If
+ Dim a0 As MalVal = ast(0)
+ Dim a0sym As String
+ If TypeOf a0 is MalSymbol Then
+ a0sym = DirectCast(a0,MalSymbol).getName()
+ Else
+ a0sym = "__<*fn*>__"
+ End If
+
+ Select a0sym
+ Case "def!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "let*"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim key As MalSymbol
+ Dim val as MalVal
+ Dim let_env As new MalEnv(env)
+ For i As Integer = 0 To (DirectCast(a1,MalList)).size()-1 Step 2
+ key = DirectCast(DirectCast(a1,MalList)(i),MalSymbol)
+ val = DirectCast(a1,MalList)(i+1)
+ let_env.do_set(key, EVAL(val, let_env))
+ Next
+ orig_ast = a2
+ env = let_env
+ Case "quote"
+ return ast(1)
+ Case "quasiquote"
+ orig_ast = quasiquote(ast(1))
+ Case "defmacro!"
+ Dim a1 As MalVal = ast(1)
+ Dim a2 As MalVal = ast(2)
+ Dim res As MalVal = EVAL(a2, env)
+ DirectCast(res,MalFunc).setMacro()
+ env.do_set(DirectCast(a1,MalSymbol), res)
+ return res
+ Case "macroexpand"
+ Dim a1 As MalVal = ast(1)
+ return macroexpand(a1, env)
+ Case "try*"
+ Try
+ return EVAL(ast(1), env)
+ Catch e As Exception
+ If ast.size() > 2 Then
+ Dim exc As MalVal
+ Dim a2 As MalVal = ast(2)
+ Dim a20 As MalVal = DirectCast(a2,MalList)(0)
+ If DirectCast(a20,MalSymbol).getName() = "catch*" Then
+ If TypeOf e Is Mal.types.MalException Then
+ exc = DirectCast(e,Mal.types.MalException).getValue()
+ Else
+ exc = New MalString(e.StackTrace)
+ End If
+ return EVAL(
+ DirectCast(a2,MalList)(2),
+ New MalEnv(env,
+ DirectCast(a2,MalList).slice(1,2),
+ New MalList(exc)))
+ End If
+ Throw e
+ End If
+ End Try
+ Case "do"
+ eval_ast(ast.slice(1, ast.size()-1), env)
+ orig_ast = ast(ast.size()-1)
+ Case "if"
+ Dim a1 As MalVal = ast(1)
+ Dim cond As MalVal = EVAL(a1, env)
+ If cond Is Mal.types.Nil or cond Is Mal.types.MalFalse Then
+ ' eval false slot form
+ If ast.size() > 3 Then
+ orig_ast = ast(3)
+ Else
+ return Mal.types.Nil
+ End If
+ Else
+ ' eval true slot form
+ orig_ast = ast(2)
+
+ End If
+ Case "fn*"
+ Dim fc As New FClosure()
+ fc.ast = ast(2)
+ fc.params = DirectCast(ast(1),MalLIst)
+ fc.env = env
+ Dim f As Func(Of MalList, MalVal) = AddressOf fc.fn
+ Dim mf As new MalFunc(ast(2), env,
+ DirectCast(ast(1),MalList), f)
+ return DirectCast(mf,MalVal)
+ Case Else
+ Dim el As MalList = DirectCast(eval_ast(ast, env), MalList)
+ Dim f As MalFunc = DirectCast(el(0), MalFunc)
+ Dim fnast As MalVal = f.getAst()
+ If not fnast Is Nothing
+ orig_ast = fnast
+ env = f.genEnv(el.rest())
+ Else
+ Return f.apply(el.rest())
+ End If
+ End Select
+
+ Loop While True
+ End Function
+
+ ' print
+ Shared Function PRINT(exp As MalVal) As String
+ return printer._pr_str(exp, TRUE)
+ End Function
+
+ ' repl
+ Shared repl_env As MalEnv
+
+ Shared Function REP(str As String) As String
+ Return PRINT(EVAL(READ(str), repl_env))
+ End Function
+
+ Shared Function do_eval(args As MalList) As MalVal
+ Return EVAL(args(0), repl_env)
+ End Function
+
+ Shared Function Main As Integer
+ Dim args As String() = Environment.GetCommandLineArgs()
+
+ repl_env = New MalEnv(Nothing)
+
+ ' core.vb: defined using VB.NET
+ For Each entry As KeyValuePair(Of String,MalVal) In core.ns()
+ repl_env.do_set(new MalSymbol(entry.Key), entry.Value)
+ Next
+ repl_env.do_set(new MalSymbol("eval"), new MalFunc(AddressOf do_eval))
+ Dim fileIdx As Integer = 1
+ If args.Length > 1 AndAlso args(1) = "--raw" Then
+ Mal.readline.SetMode(Mal.readline.Modes.Raw)
+ fileIdx = 2
+ End If
+ Dim argv As New MalList()
+ For i As Integer = fileIdx+1 To args.Length-1
+ argv.conj_BANG(new MalString(args(i)))
+ Next
+ repl_env.do_set(new MalSymbol("*ARGV*"), argv)
+
+ ' core.mal: defined using the language itself
+ REP("(def! *host-language* ""VB.NET"")")
+ 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 args.Length > fileIdx Then
+ REP("(load-file """ & args(fileIdx) & """)")
+ return 0
+ End If
+
+ ' repl loop
+ Dim line As String
+ REP("(println (str ""Mal ["" *host-language* ""]""))")
+ Do
+ Try
+ line = Mal.readline.Readline("user> ")
+ If line is Nothing Then
+ Exit Do
+ End If
+ If line = "" Then
+ Continue Do
+ End If
+ Catch e As IOException
+ Console.WriteLine("IOException: " & e.Message)
+ End Try
+ Try
+ Console.WriteLine(REP(line))
+ Catch e As Mal.types.MalException
+ Console.WriteLine("Error: " & _
+ printer._pr_str(e.getValue(), False))
+ Continue Do
+ Catch e As Exception
+ Console.WriteLine("Error: " & e.Message)
+ Console.WriteLine(e.StackTrace)
+ Continue Do
+ End Try
+ Loop While True
+ End function
+ End Class
+End Namespace
diff --git a/vb/types.vb b/vb/types.vb
new file mode 100644
index 0000000..4da75bd
--- /dev/null
+++ b/vb/types.vb
@@ -0,0 +1,460 @@
+Imports System
+Imports System.Collections.Generic
+Imports System.Text.RegularExpressions
+Imports Mal
+
+namespace Mal
+ Public Class types
+ '
+ ' Exceptiosn/Errors
+ '
+ Public Class MalThrowable
+ Inherits Exception
+ Public Sub New()
+ MyBase.New()
+ End Sub
+ Public Sub New(msg As String)
+ MyBase.New(msg)
+ End Sub
+ End Class
+ Public Class MalError
+ Inherits MalThrowable
+ Public Sub New(msg As String)
+ MyBase.New(msg)
+ End Sub
+ End Class
+ Public Class MalContinue
+ Inherits MalThrowable
+ End Class
+
+ ' Thrown by throw function
+ Public Class MalException
+ Inherits MalThrowable
+ Private value As MalVal
+
+ 'string Message
+ Public Sub New(new_value As MalVal)
+ value = new_value
+ End Sub
+ Public Sub New(new_value As String)
+ MyBase.New(new_value)
+ value = New MalString(new_value)
+ End Sub
+ Public Function getValue() As MalVal
+ return value
+ End Function
+ End Class
+
+ '
+ ' General functions
+ '
+ Public Shared Function _equal_Q(a As MalVal, b As MalVal) As Boolean
+ Dim ota As Type = a.GetType()
+ Dim otb As Type = b.GetType()
+ If not (ota = otb Or
+ (TypeOf a Is MalList and TypeOf b Is MalList)) Then
+ return False
+ Else
+ If TypeOf a Is MalInt Then
+ return DirectCast(a,MalInt).getValue() =
+ DirectCast(b,MalInt).getValue()
+ Else If TypeOf a Is MalSymbol Then
+ return DirectCast(a,MalSymbol).getName() =
+ DirectCast(b,MalSymbol).getName()
+ Else If TypeOf a Is MalString Then
+ return DirectCast(a,MalString).getValue() =
+ DirectCast(b,MalString).getValue()
+ Else If TypeOf a Is MalList Then
+ If DirectCast(a,MalList).size() <>
+ DirectCast(b,MalList).size()
+ return False
+ End If
+ for i As Integer = 0 To DirectCast(a,MalList).size()-1
+ If not _equal_Q(DirectCast(a,MalList)(i),
+ DirectCast(b,MalList)(i))
+ return False
+ End If
+ Next
+ return True
+ Else
+ return a Is b
+ End If
+ End If
+ End Function
+
+
+ Public MustInherit Class MalVal
+ Private meta As MalVal = Nil
+ Public Overridable Function copy() As MalVal
+ return DirectCast(Me.MemberwiseClone(),MalVal)
+ End Function
+
+ ' Default is just to call regular toString()
+ Public Overridable Function ToString() As String
+ throw New MalException("ToString called on abstract MalVal")
+ End Function
+ Public Overridable Function ToString(print_readably As Boolean) As String
+ return Me.ToString()
+ End Function
+ Public Function getMeta() As MalVal
+ return meta
+ End Function
+ Public Function setMeta(m As MalVal) As MalVal
+ meta = m
+ return Me
+ End Function
+ Public Overridable Function list_Q() As Boolean
+ return False
+ End Function
+ End Class
+
+ Public Class MalConstant
+ Inherits MalVal
+ Private value As String
+ Public Sub New(name As String)
+ value = name
+ End Sub
+ Public Shadows Function copy() As MalConstant
+ return Me
+ End Function
+
+ Public Overrides Function ToString() As String
+ return value
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ return value
+ End Function
+ End Class
+
+ Public Shared Nil As MalConstant = New MalConstant("nil")
+ Public Shared MalTrue As MalConstant = New MalConstant("true")
+ Public Shared MalFalse As MalConstant = New MalConstant("false")
+
+ Public Class MalInt
+ Inherits MalVal
+ Private value As Int64
+ Public Sub New(v As Int64)
+ value = v
+ End Sub
+ Public Shadows Function copy() As MalInt
+ return Me
+ End Function
+
+ Public Function getValue() As Int64
+ return value
+ End Function
+ Public Overrides Function ToString() As String
+ return value.ToString()
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ return value.ToString()
+ End Function
+ Public Shared Operator <(a As MalInt, b As Malint) As MalConstant
+ If a.getValue() < b.getValue() Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Operator
+ Public Shared Operator <=(a As MalInt, b As Malint) As MalConstant
+ If a.getValue() <= b.getValue() Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Operator
+ Public Shared Operator >(a As MalInt, b As Malint) As MalConstant
+ If a.getValue() > b.getValue() Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Operator
+ Public Shared Operator >=(a As MalInt, b As Malint) As MalConstant
+ If a.getValue() >= b.getValue() Then
+ return MalTrue
+ Else
+ return MalFalse
+ End If
+ End Operator
+ Public Shared Operator +(a As MalInt, b As Malint) As MalInt
+ return new MalInt(a.getValue() + b.getValue())
+ End Operator
+ Public Shared Operator -(a As MalInt, b As Malint) As MalInt
+ return new MalInt(a.getValue() - b.getValue())
+ End Operator
+ Public Shared Operator *(a As MalInt, b As Malint) As MalInt
+ return new MalInt(a.getValue() * b.getValue())
+ End Operator
+ Public Shared Operator /(a As MalInt, b As Malint) As MalInt
+ return new MalInt(a.getValue() / b.getValue())
+ End Operator
+ End Class
+
+ Public Class MalSymbol
+ Inherits MalVal
+ Private value As String
+ Public Sub New(v As String)
+ value = v
+ End Sub
+ Public Sub New(v As MalString)
+ value = v.getValue()
+ End Sub
+ Public Shadows Function copy() As MalSymbol
+ return Me
+ End Function
+
+ Public Function getName() As String
+ return value
+ End Function
+ Public Overrides Function ToString() As String
+ return value
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ return value
+ End Function
+ End Class
+
+ Public Class MalString
+ Inherits MalVal
+ Private value As String
+ Public Sub New(v As String)
+ value = v
+ End Sub
+ Public Shadows Function copy() As MalString
+ return Me
+ End Function
+
+ Public Function getValue() As String
+ return value
+ End Function
+ Public Overrides Function ToString() As String
+ return """" & value & """"
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ If value.Length > 0 AndAlso value(0) = ChrW(&H029e) Then
+ return ":" & value.Substring(1)
+ Else If print_readably Then
+ return """" & _
+ value.Replace("\", "\\") _
+ .Replace("""", "\""") _
+ .Replace(Environment.NewLine, "\n") & _
+ """"
+ Else
+ return value
+ End If
+ End Function
+ End Class
+
+
+ Public Class MalList
+ Inherits MalVal
+ Public start As String = "("
+ Public last As String = ")"
+ Private value As List(Of MalVal)
+ Public Sub New()
+ value = New List(Of MalVal)
+ End Sub
+ Public Sub New(val As List(Of MalVal))
+ value = val
+ End Sub
+ Public Sub New(ParamArray mvs() As MalVal)
+ value = New List(Of MalVal)
+ conj_BANG(mvs)
+ End Sub
+
+ Public Function getValue() As List(Of MalVal)
+ return value
+ End Function
+ Public Overrides Function list_Q() As Boolean
+ return True
+ End Function
+
+ Public Overrides Function ToString() As String
+ return start & printer.join(value, " ", true) & last
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ return start & printer.join(value, " ", print_readably) & last
+ End Function
+
+ Public Function conj_BANG(ParamArray mvs() As MalVal) As MalList
+ For i As Integer = 0 To mvs.Length-1
+ value.Add(mvs(i))
+ Next
+ return Me
+ End Function
+
+ Public Function size() As Int64
+ return value.Count
+ End Function
+ Public Function nth(ByVal idx As Integer) As MalVal
+ If value.Count > idx Then
+ return value(idx)
+ Else
+ return Nil
+ End If
+ End Function
+ Default Public ReadOnly Property Item(idx As Integer) As MalVal
+ Get
+ If value.Count > idx then
+ return value(idx)
+ Else
+ return Nil
+ End If
+ End Get
+ End Property
+ Public Function rest() As MalList
+ If size() > 0 Then
+ return New MalList(value.GetRange(1, value.Count-1))
+ Else
+ return New MalList()
+ End If
+ End Function
+ Public Overridable Function slice(start As Int64) As MalList
+ return New MalList(value.GetRange(start, value.Count-start))
+ End Function
+ Public Overridable Function slice(start As Int64, last As Int64) As MalList
+ return New MalList(value.GetRange(start, last-start))
+ End Function
+ End Class
+
+ Public Class MalVector
+ Inherits MalList
+' ' Same implementation except for instantiation methods
+ Public Sub New()
+ MyBase.New()
+ start = "["
+ last = "]"
+ End Sub
+ Public Sub New(val As List(Of MalVal))
+ MyBase.New(val)
+ start = "["
+ last = "]"
+ End Sub
+
+ Public Overrides Function list_Q() As Boolean
+ return False
+ End Function
+
+ Public Overrides Function slice(start As Int64, last As Int64) As MalList
+ Dim val As List(Of MalVal) = Me.getValue()
+ return New MalVector(val.GetRange(start, val.Count-start))
+ End Function
+ End Class
+
+ Public Class MalHashMap
+ Inherits MalVal
+ Private value As Dictionary(Of string, MalVal)
+ Public Sub New(val As Dictionary(Of String, MalVal))
+ value = val
+ End Sub
+ Public Sub New(lst As MalList)
+ value = New Dictionary(Of String, MalVal)
+ assoc_BANG(lst)
+ End Sub
+ Public Shadows Function copy() As MalHashMap
+ Dim new_self As MalHashMap = DirectCast(Me.MemberwiseClone(),MalHashMap)
+ new_self.value = New Dictionary(Of String, MalVal)(value)
+ return new_self
+ End Function
+
+ Public Function getValue() As Dictionary(Of String, MalVal)
+ return value
+ End Function
+
+ Public Overrides Function ToString() As String
+ return "{" & printer.join(value, " ", true) & "}"
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ return "{" & printer.join(value, " ", print_readably) & "}"
+ End Function
+
+ Public Function assoc_BANG(lst As MalList) As MalHashMap
+ For i As Integer = 0 To lst.size()-1 Step 2
+ value(DirectCast(lst(i),MalString).getValue()) = lst(i+1)
+ Next
+ return Me
+ End Function
+
+ Public Function dissoc_BANG(lst As MalList) As MalHashMap
+ for i As Integer = 0 To lst.size()-1
+ value.Remove(DirectCast(lst.nth(i),MalString).getValue())
+ Next
+ return Me
+ End Function
+ End Class
+
+ Public Class MalAtom
+ Inherits MalVal
+ Private value As MalVal
+ Public Sub New(val As MalVal)
+ value = val
+ End Sub
+ 'Public MalAtom copy() { return New MalAtom(value) }
+ Public Function getValue() As MalVal
+ return value
+ End Function
+ Public Function setValue(val As MalVal) As MalVal
+ value = val
+ return value
+ End Function
+ Public Overrides Function ToString() As String
+ return "(atom " & printer._pr_str(value, true) & ")"
+ End Function
+ Public Overrides Function ToString(print_readably As Boolean) As String
+ return "(atom " & printer._pr_str(value, print_readably) & ")"
+ End Function
+ End Class
+
+ Public Class MalFunc
+ Inherits MalVal
+ Private fn As Func(Of MalList, MalVal) = Nothing
+ Private ast As MalVal = Nothing
+ Private env As Mal.env.Env = Nothing
+ Private fparams As MalList
+ Private macro As Boolean = False
+ Public Sub New(new_fn As Func(Of MalList, MalVal))
+ fn = new_fn
+ End Sub
+ Public Sub New(new_ast As MalVal, new_env As Mal.env.Env,
+ new_fparams As MalList, new_fn As Func(Of MalList, MalVal))
+ fn = new_fn
+ ast = new_ast
+ env = new_env
+ fparams = new_fparams
+ End Sub
+
+ Public Overrides Function ToString() As String
+ If Not ast Is Nothing Then
+ return "<fn* " & Mal.printer._pr_str(fparams,true) &
+ " " & Mal.printer._pr_str(ast, true) & ">"
+ Else
+ return "<builtin_function " & fn.ToString() & ">"
+ End If
+ End Function
+
+ Public Function apply(args As MalList) As MalVal
+ return fn(args)
+ End Function
+
+ Public Function getAst() As MalVal
+ return ast
+ End Function
+ Public Function getEnv() As Mal.env.Env
+ return env
+ End Function
+ Public Function getFParams() As MalList
+ return fparams
+ End Function
+ Public Function genEnv(args As MalList) As Mal.env.Env
+ return New Mal.env.Env(env, fparams, args)
+ End Function
+ Public Function isMacro() As Boolean
+ return macro
+ End Function
+ Public Sub setMacro()
+ macro = true
+ End Sub
+ End Class
+ End Class
+End Namespace