diff options
| author | Joel Martin <github@martintribe.org> | 2015-03-02 21:33:10 -0600 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2015-03-02 21:33:10 -0600 |
| commit | 835fb7d8b06e2b44792a97ac89994658bf6d00af (patch) | |
| tree | 578f67726ab9e3ce5fcbc50220e9761a66c5ddf1 | |
| parent | 6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (diff) | |
| parent | 8a98ef9a3f3a6b6d05d02dc305a0c886c907e0f3 (diff) | |
| download | mal-835fb7d8b06e2b44792a97ac89994658bf6d00af.tar.gz mal-835fb7d8b06e2b44792a97ac89994658bf6d00af.zip | |
Merge branch 'master' into gh-pages
Conflicts:
.gitignore
506 files changed, 39718 insertions, 1608 deletions
@@ -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. + + @@ -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)) @@ -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 @@ -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 $^ @@ -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}, @@ -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 @@ -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); @@ -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 @@ -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); } @@ -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 @@ -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)) @@ -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}, @@ -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; @@ -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) @@ -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, @@ -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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 + + + +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 Binary files differnew file mode 100644 index 0000000..270f427 --- /dev/null +++ b/process/step0_repl.png 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 Binary files differnew file mode 100644 index 0000000..29cc1cf --- /dev/null +++ b/process/step1_read_print.png 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=\"\">+</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 Binary files differnew file mode 100644 index 0000000..da9c805 --- /dev/null +++ b/process/step2_eval.png 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=\"\">"apply"</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=\"\">+</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 Binary files differnew file mode 100644 index 0000000..7d0c3b2 --- /dev/null +++ b/process/step3_env.png 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=\"\">"apply"</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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * /</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 Binary files differnew file mode 100644 index 0000000..e3cfc7a --- /dev/null +++ b/process/step4_if_fn_do.png 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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * /</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=\"\">"apply"</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 Binary files differnew file mode 100644 index 0000000..d4d48d6 --- /dev/null +++ b/process/step5_tco.png 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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * /</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=\"\">"apply"</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 Binary files differnew file mode 100644 index 0000000..773247e --- /dev/null +++ b/process/step6_file.png 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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * /</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=\"\">"apply"</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 Binary files differnew file mode 100644 index 0000000..ecf852f --- /dev/null +++ b/process/step7_quote.png 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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * /</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=\"\">"apply"</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 Binary files differnew file mode 100644 index 0000000..208f456 --- /dev/null +++ b/process/step8_macros.png 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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * /</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=\"\">"apply"</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 Binary files differnew file mode 100644 index 0000000..401af23 --- /dev/null +++ b/process/step9_try.png 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=\"\">= </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=\"\">< <= > >= </span></span><span style=\"font-weight:bold;font-size:10px;\"><span style=\"\">+ - * / </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=\"\">"apply"</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 Binary files differnew file mode 100644 index 0000000..28dcb5b --- /dev/null +++ b/process/stepA_mal.png 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) @@ -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 +) @@ -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) @@ -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 |
