From 107d969497d482b07c33c4f28123727fa0a0b263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Inge=20J=C3=B8rgensen?= Date: Sat, 28 Feb 2015 00:36:27 +0100 Subject: Ruby: Updated for Ruby 1.9+ --- README.md | 2 +- ruby/core.rb | 4 ++-- ruby/mal_readline.rb | 2 +- ruby/printer.rb | 2 +- ruby/reader.rb | 2 +- ruby/step0_repl.rb | 3 +-- ruby/step1_read_print.rb | 9 ++++----- ruby/step2_eval.rb | 9 ++++----- ruby/step3_env.rb | 11 +++++------ ruby/step4_if_fn_do.rb | 13 ++++++------- ruby/step5_tco.rb | 13 ++++++------- ruby/step6_file.rb | 13 ++++++------- ruby/step7_quote.rb | 13 ++++++------- ruby/step8_macros.rb | 13 ++++++------- ruby/step9_try.rb | 13 ++++++------- ruby/stepA_interop.rb | 13 ++++++------- ruby/types.rb | 2 +- 17 files changed, 63 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 612743c..9dac314 100644 --- a/README.md +++ b/README.md @@ -286,7 +286,7 @@ cd racket ./stepX_YYY.rb ``` -### Ruby (1.8) +### Ruby (1.9+) ``` cd ruby diff --git a/ruby/core.rb b/ruby/core.rb index d55100c..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}, 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 37d338a..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 diff --git a/ruby/reader.rb b/ruby/reader.rb index 641e65c..badc6ec 100644 --- a/ruby/reader.rb +++ b/ruby/reader.rb @@ -1,4 +1,4 @@ -require "types" +require_relative "types" class Reader def initialize(tokens) 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_try.rb b/ruby/step9_try.rb index 74d0f59..533853b 100644 --- a/ruby/step9_try.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) diff --git a/ruby/stepA_interop.rb b/ruby/stepA_interop.rb index 6123293..3fb4af0 100644 --- a/ruby/stepA_interop.rb +++ b/ruby/stepA_interop.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/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 -- cgit v1.2.3 From 6a767b0d00a9a3ad578bdb8d85eddbd1afb9be35 Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 27 Feb 2015 17:57:34 -0800 Subject: small typo in example --- process/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/guide.md b/process/guide.md index 734dc32..649aee6 100644 --- a/process/guide.md +++ b/process/guide.md @@ -321,7 +321,7 @@ manually try some simple inputs: * ` abc ` -> `abc` * `(123 456)` -> `(123 456)` * `( 123 456 789 ) ` -> `(123 456 789)` - * `( + 2 (+ 3 4) ) ` -> `(+ 2 (* 3 4))` + * `( + 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. -- cgit v1.2.3 From fc03712f7a56e450debfbfeb47d11301a94046ac Mon Sep 17 00:00:00 2001 From: Erik Chancy Date: Sat, 28 Feb 2015 18:00:27 +1100 Subject: Update guide.md --- process/guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/process/guide.md b/process/guide.md index 734dc32..669d6a6 100644 --- a/process/guide.md +++ b/process/guide.md @@ -68,8 +68,8 @@ git clone git@github.com:YOUR_NAME/mal.git cd mal ``` -* Make a new directory for your implementation. For example, if you -* language is called "quux": +* Make a new directory for your implementation. For example, if your +language is called "quux": ``` mkdir quux ``` -- cgit v1.2.3 From c10dcb94671ac3c29e503896396d4a413898f415 Mon Sep 17 00:00:00 2001 From: Anton Trunov Date: Sat, 28 Feb 2015 13:55:55 +0300 Subject: guide: insert missing word --- process/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/guide.md b/process/guide.md index 734dc32..d8d6db4 100644 --- a/process/guide.md +++ b/process/guide.md @@ -15,7 +15,7 @@ So jump right in (er ... start the climb)! You might already have a language in mind that you want to use. Technically speaking, mal can be implemented in any sufficiently -complete programming (i.e. Turing complete), however, there are a few +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: -- cgit v1.2.3 From 27696157525984853f7d980b674c05c7ebf857ff Mon Sep 17 00:00:00 2001 From: joelpickup Date: Sat, 28 Feb 2015 13:48:13 +0000 Subject: grammar fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 612743c..9efef1f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Description -Mal is an Clojure inspired Lisp interpreter. +Mal is a Clojure inspired Lisp interpreter. Mal is implemented in 26 different languages: -- cgit v1.2.3 From 2ab1e5845c213a9951bee46a0c991202e6c46d5c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 28 Feb 2015 10:35:04 -0600 Subject: Multiple: interop enhancements. --- cs/stepA_interop.cs | 4 ++-- docs/TODO | 8 ++++++++ js/stepA_interop.js | 8 ++++++-- js/types.js | 4 ++++ php/stepA_interop.php | 14 +++++++++++++- ps/interop.ps | 4 ++-- ruby/stepA_interop.rb | 6 +++++- rust/Makefile | 7 +++++-- 8 files changed, 45 insertions(+), 10 deletions(-) diff --git a/cs/stepA_interop.cs b/cs/stepA_interop.cs index 2e8fc12..6531d50 100644 --- a/cs/stepA_interop.cs +++ b/cs/stepA_interop.cs @@ -231,10 +231,10 @@ namespace Mal { } repl_env.set(new MalSymbol("eval"), new MalFunc( a => EVAL(a[0], repl_env))); - int fileIdx = 1; + int fileIdx = 0; if (args.Length > 0 && args[0] == "--raw") { Mal.readline.mode = Mal.readline.Mode.Raw; - fileIdx = 2; + fileIdx = 1; } MalList _argv = new MalList(); for (int i=fileIdx; i < args.Length; i++) { diff --git a/docs/TODO b/docs/TODO index 741d024..e5b135b 100644 --- a/docs/TODO +++ b/docs/TODO @@ -1,4 +1,6 @@ All: + - Finish guide.md + - rename stepA_interop to stepA_mal - test to check args set properly - test to make sure slurp captures final newline @@ -57,6 +59,8 @@ C: C#: - 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) @@ -167,6 +171,10 @@ Future Implementations: - http://api.haxe.org/ - http://haxe.us/haxe_tutorial.html + - Julia + + - Nim + - Objective-C: - Pascal: diff --git a/js/stepA_interop.js b/js/stepA_interop.js index 456c006..d879cd3 100644 --- a/js/stepA_interop.js +++ b/js/stepA_interop.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 @@ -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); diff --git a/js/types.js b/js/types.js index 848a484..e3901b7 100644 --- a/js/types.js +++ b/js/types.js @@ -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; } diff --git a/php/stepA_interop.php b/php/stepA_interop.php index 8c67c66..1dc3b04 100644 --- a/php/stepA_interop.php +++ b/php/stepA_interop.php @@ -109,7 +109,19 @@ function MAL_EVAL($ast, $env) { 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]; diff --git a/ps/interop.ps b/ps/interop.ps index fb3b88d..8020ab0 100644 --- a/ps/interop.ps +++ b/ps/interop.ps @@ -1,6 +1,6 @@ -% ps_val -> ps2mal -> mal_val +% [ ps_val1...] -> ps2mal -> [ mal_val1...] /ps2mal { - % convert a PS value to a Mal value (recursively) + % convert returned values to Mal types [ exch { %forall returned values dup == diff --git a/ruby/stepA_interop.rb b/ruby/stepA_interop.rb index 6123293..1eff1f1 100644 --- a/ruby/stepA_interop.rb +++ b/ruby/stepA_interop.rb @@ -96,7 +96,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/rust/Makefile b/rust/Makefile index 537dac5..6910356 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -8,19 +8,22 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) ##################### -SRCS = step0_repl.rs step1_read_print.rs step2_eval.rs step3_env.rs \ +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_interop.rs BINS = $(SRCS:%.rs=target/%) ##################### -all: $(BINS) mal +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 -- cgit v1.2.3 From 699f0ad23aca21076edb6a51838d879ca580ffd5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 28 Feb 2015 10:38:17 -0600 Subject: Add long running perf test. --- Makefile | 4 +++- perf.mal | 20 ++++++++++++++++++++ tests/perf3.mal | 28 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/perf3.mal diff --git a/Makefile b/Makefile index 47c7829..e30a6f6 100644 --- a/Makefile +++ b/Makefile @@ -193,5 +193,7 @@ $(IMPL_PERF): echo 'Running: $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf1.mal)'; \ $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf1.mal); \ echo 'Running: $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf2.mal)'; \ - $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf2.mal)) + $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf2.mal); \ + echo 'Running: $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf3.mal)'; \ + $(call $(impl)_RUNSTEP,stepA,$(call $(impl)_STEP_TO_PROG,stepA),../tests/perf3.mal)) diff --git a/perf.mal b/perf.mal index 94da2ff..83bbc0d 100644 --- a/perf.mal +++ b/perf.mal @@ -5,3 +5,23 @@ (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/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") -- cgit v1.2.3 From 90f618cbe7ac7740accf501a75be6972bd95be1a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 28 Feb 2015 11:09:54 -0600 Subject: All: rename stepA_interop to stepA_mal Also, add missed postscript interop tests. --- .gitignore | 2 +- Makefile | 2 +- README.md | 2 +- bash/Makefile | 2 +- bash/stepA_interop.sh | 283 ------------------ bash/stepA_mal.sh | 283 ++++++++++++++++++ bash/tests/stepA_interop.mal | 17 -- bash/tests/stepA_mal.mal | 17 ++ c/Makefile | 4 +- c/stepA_interop.c | 356 ---------------------- c/stepA_mal.c | 356 ++++++++++++++++++++++ c/tests/stepA_interop.mal | 23 -- c/tests/stepA_mal.mal | 23 ++ clojure/Makefile | 2 +- clojure/src/stepA_interop.clj | 181 ----------- clojure/src/stepA_mal.clj | 181 +++++++++++ clojure/tests/stepA_interop.mal | 17 -- clojure/tests/stepA_mal.mal | 17 ++ coffee/Makefile | 2 +- coffee/stepA_interop.coffee | 142 --------- coffee/stepA_mal.coffee | 142 +++++++++ coffee/tests/stepA_interop.mal | 24 -- coffee/tests/stepA_mal.mal | 24 ++ cs/Makefile | 4 +- cs/stepA_interop.cs | 285 ------------------ cs/stepA_mal.cs | 285 ++++++++++++++++++ docs/TODO | 5 +- docs/step_notes.txt | 2 +- forth/Makefile | 2 +- forth/stepA_interop.fs | 390 ------------------------ forth/stepA_mal.fs | 390 ++++++++++++++++++++++++ forth/tests/stepA_interop.mal | 41 --- forth/tests/stepA_mal.mal | 41 +++ go/Makefile | 4 +- go/src/stepA_interop/stepA_interop.go | 306 ------------------- go/src/stepA_mal/stepA_mal.go | 306 +++++++++++++++++++ haskell/Makefile | 2 +- haskell/stepA_interop.hs | 255 ---------------- haskell/stepA_mal.hs | 255 ++++++++++++++++ java/Makefile | 2 +- java/pom.xml | 4 +- java/src/main/java/mal/stepA_interop.java | 301 ------------------- java/src/main/java/mal/stepA_mal.java | 301 +++++++++++++++++++ js/Makefile | 2 +- js/stepA_interop.js | 203 ------------- js/stepA_mal.js | 203 +++++++++++++ js/tests/stepA_interop.mal | 24 -- js/tests/stepA_mal.mal | 24 ++ lua/Makefile | 2 +- lua/stepA_interop.lua | 200 ------------- lua/stepA_mal.lua | 200 +++++++++++++ make/Makefile | 4 +- make/stepA_interop.mk | 193 ------------ make/stepA_mal.mk | 193 ++++++++++++ make/tests/stepA_interop.mal | 19 -- make/tests/stepA_mal.mal | 19 ++ mal/Makefile | 2 +- mal/stepA_interop.mal | 181 ----------- mal/stepA_mal.mal | 181 +++++++++++ matlab/Makefile | 2 +- matlab/stepA_interop.m | 226 -------------- matlab/stepA_mal.m | 226 ++++++++++++++ miniMAL/Makefile | 2 +- miniMAL/stepA_interop.json | 171 ----------- miniMAL/stepA_mal.json | 171 +++++++++++ ocaml/Makefile | 6 +- ocaml/stepA_interop.ml | 159 ---------- ocaml/stepA_mal.ml | 159 ++++++++++ perl/Makefile | 2 +- perl/stepA_interop.pl | 265 ----------------- perl/stepA_mal.pl | 265 +++++++++++++++++ perl/tests/stepA_interop.mal | 22 -- perl/tests/stepA_mal.mal | 22 ++ php/Makefile | 2 +- php/stepA_interop.php | 229 -------------- php/stepA_mal.php | 229 ++++++++++++++ php/tests/stepA_interop.mal | 25 -- php/tests/stepA_mal.mal | 25 ++ process/guide.md | 6 +- process/stepA_interop.gliffy | 1 - process/stepA_interop.png | Bin 83168 -> 0 bytes process/stepA_interop.txt | 136 --------- process/stepA_mal.gliffy | 1 + process/stepA_mal.png | Bin 0 -> 83168 bytes process/stepA_mal.txt | 136 +++++++++ ps/Makefile | 2 +- ps/stepA_interop.ps | 298 ------------------- ps/stepA_mal.ps | 298 +++++++++++++++++++ ps/tests/stepA_mal.mal | 23 ++ python/Makefile | 2 +- python/stepA_interop.py | 177 ----------- python/stepA_mal.py | 177 +++++++++++ r/Makefile | 2 +- r/stepA_interop.r | 198 ------------ r/stepA_mal.r | 198 ++++++++++++ racket/Makefile | 2 +- racket/stepA_interop.rkt | 163 ---------- racket/stepA_mal.rkt | 163 ++++++++++ ruby/Makefile | 2 +- ruby/stepA_interop.rb | 184 ------------ ruby/stepA_mal.rb | 184 ++++++++++++ ruby/tests/stepA_interop.mal | 27 -- ruby/tests/stepA_mal.mal | 27 ++ rust/Cargo.toml | 2 +- rust/Makefile | 4 +- rust/src/stepA_interop.rs | 479 ------------------------------ rust/src/stepA_mal.rs | 479 ++++++++++++++++++++++++++++++ scala/Makefile | 2 +- scala/stepA_interop.scala | 229 -------------- scala/stepA_mal.scala | 229 ++++++++++++++ vb/Makefile | 4 +- vb/stepA_interop.vb | 317 -------------------- vb/stepA_mal.vb | 317 ++++++++++++++++++++ 113 files changed, 6816 insertions(+), 6794 deletions(-) delete mode 100755 bash/stepA_interop.sh create mode 100755 bash/stepA_mal.sh delete mode 100644 bash/tests/stepA_interop.mal create mode 100644 bash/tests/stepA_mal.mal delete mode 100644 c/stepA_interop.c create mode 100644 c/stepA_mal.c delete mode 100644 c/tests/stepA_interop.mal create mode 100644 c/tests/stepA_mal.mal delete mode 100644 clojure/src/stepA_interop.clj create mode 100644 clojure/src/stepA_mal.clj delete mode 100644 clojure/tests/stepA_interop.mal create mode 100644 clojure/tests/stepA_mal.mal delete mode 100644 coffee/stepA_interop.coffee create mode 100644 coffee/stepA_mal.coffee delete mode 100644 coffee/tests/stepA_interop.mal create mode 100644 coffee/tests/stepA_mal.mal delete mode 100644 cs/stepA_interop.cs create mode 100644 cs/stepA_mal.cs delete mode 100644 forth/stepA_interop.fs create mode 100644 forth/stepA_mal.fs delete mode 100644 forth/tests/stepA_interop.mal create mode 100644 forth/tests/stepA_mal.mal delete mode 100644 go/src/stepA_interop/stepA_interop.go create mode 100644 go/src/stepA_mal/stepA_mal.go delete mode 100644 haskell/stepA_interop.hs create mode 100644 haskell/stepA_mal.hs delete mode 100644 java/src/main/java/mal/stepA_interop.java create mode 100644 java/src/main/java/mal/stepA_mal.java delete mode 100644 js/stepA_interop.js create mode 100644 js/stepA_mal.js delete mode 100644 js/tests/stepA_interop.mal create mode 100644 js/tests/stepA_mal.mal delete mode 100755 lua/stepA_interop.lua create mode 100755 lua/stepA_mal.lua delete mode 100644 make/stepA_interop.mk create mode 100644 make/stepA_mal.mk delete mode 100644 make/tests/stepA_interop.mal create mode 100644 make/tests/stepA_mal.mal delete mode 100644 mal/stepA_interop.mal create mode 100644 mal/stepA_mal.mal delete mode 100644 matlab/stepA_interop.m create mode 100644 matlab/stepA_mal.m delete mode 100644 miniMAL/stepA_interop.json create mode 100644 miniMAL/stepA_mal.json delete mode 100644 ocaml/stepA_interop.ml create mode 100644 ocaml/stepA_mal.ml delete mode 100644 perl/stepA_interop.pl create mode 100644 perl/stepA_mal.pl delete mode 100644 perl/tests/stepA_interop.mal create mode 100644 perl/tests/stepA_mal.mal delete mode 100644 php/stepA_interop.php create mode 100644 php/stepA_mal.php delete mode 100644 php/tests/stepA_interop.mal create mode 100644 php/tests/stepA_mal.mal delete mode 100644 process/stepA_interop.gliffy delete mode 100644 process/stepA_interop.png delete mode 100644 process/stepA_interop.txt create mode 100644 process/stepA_mal.gliffy create mode 100644 process/stepA_mal.png create mode 100644 process/stepA_mal.txt delete mode 100644 ps/stepA_interop.ps create mode 100644 ps/stepA_mal.ps create mode 100644 ps/tests/stepA_mal.mal delete mode 100644 python/stepA_interop.py create mode 100644 python/stepA_mal.py delete mode 100644 r/stepA_interop.r create mode 100644 r/stepA_mal.r delete mode 100755 racket/stepA_interop.rkt create mode 100755 racket/stepA_mal.rkt delete mode 100644 ruby/stepA_interop.rb create mode 100644 ruby/stepA_mal.rb delete mode 100644 ruby/tests/stepA_interop.mal create mode 100644 ruby/tests/stepA_mal.mal delete mode 100644 rust/src/stepA_interop.rs create mode 100644 rust/src/stepA_mal.rs delete mode 100644 scala/stepA_interop.scala create mode 100644 scala/stepA_mal.scala delete mode 100644 vb/stepA_interop.vb create mode 100644 vb/stepA_mal.vb diff --git a/.gitignore b/.gitignore index 07d7921..2b05376 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ c/*.o */step7_quote */step8_macros */step9_try -*/stepA_interop +*/stepA_mal cs/*.exe cs/*.dll cs/*.mdb diff --git a/Makefile b/Makefile index e30a6f6..674ad71 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ step6 = step6_file step7 = step7_quote step8 = step8_macros step9 = step9_try -stepA = stepA_interop +stepA = stepA_mal EXCLUDE_TESTS += test^bash^step5 # no stack exhaustion or completion EXCLUDE_TESTS += test^c^step5 # segfault diff --git a/README.md b/README.md index 612743c..94fdc2b 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The mal (make a lisp) steps are: * [step7_quote](process/guide.md#step7) * [step8_macros](process/guide.md#step8) * [step9_try](process/guide.md#step9) -* [stepA_interop](process/guide.md#stepA) +* [stepA_mal](process/guide.md#stepA) Mal was presented publicly for the first time in a lightning talk at diff --git a/bash/Makefile b/bash/Makefile index 5028788..dc1d1ad 100644 --- a/bash/Makefile +++ b/bash/Makefile @@ -1,5 +1,5 @@ SOURCES_BASE = types.sh reader.sh printer.sh -SOURCES_LISP = env.sh core.sh stepA_interop.sh +SOURCES_LISP = env.sh core.sh stepA_mal.sh SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) all: mal.sh diff --git a/bash/stepA_interop.sh b/bash/stepA_interop.sh deleted file mode 100755 index 7b43496..0000000 --- a/bash/stepA_interop.sh +++ /dev/null @@ -1,283 +0,0 @@ -#!/usr/bin/env bash - -source $(dirname $0)/reader.sh -source $(dirname $0)/printer.sh -source $(dirname $0)/env.sh -source $(dirname $0)/core.sh - -# read -READ () { - [ "${1}" ] && r="${1}" || READLINE - READ_STR "${r}" -} - -# eval -IS_PAIR () { - if _sequential? "${1}"; then - _count "${1}" - [[ "${r}" > 0 ]] && return 0 - fi - return 1 -} - -QUASIQUOTE () { - if ! IS_PAIR "${1}"; then - _symbol quote - _list "${r}" "${1}" - return - else - _nth "${1}" 0; local a0="${r}" - if [[ "${ANON["${a0}"]}" == "unquote" ]]; then - _nth "${1}" 1 - return - elif IS_PAIR "${a0}"; then - _nth "${a0}" 0; local a00="${r}" - if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then - _symbol concat; local a="${r}" - _nth "${a0}" 1; local b="${r}" - _rest "${1}" - QUASIQUOTE "${r}"; local c="${r}" - _list "${a}" "${b}" "${c}" - return - fi - fi - fi - _symbol cons; local a="${r}" - QUASIQUOTE "${a0}"; local b="${r}" - _rest "${1}" - QUASIQUOTE "${r}"; local c="${r}" - _list "${a}" "${b}" "${c}" - return -} - -IS_MACRO_CALL () { - if ! _list? "${1}"; then return 1; fi - _nth "${1}" 0; local a0="${r}" - if _symbol? "${a0}"; then - ENV_FIND "${2}" "${a0}" - if [[ "${r}" ]]; then - ENV_GET "${2}" "${a0}" - [ "${ANON["${r}_ismacro_"]}" ] - return $? - fi - fi - return 1 -} - -MACROEXPAND () { - local ast="${1}" env="${2}" - while IS_MACRO_CALL "${ast}" "${env}"; do - _nth "${ast}" 0; local a0="${r}" - ENV_GET "${env}" "${a0}"; local mac="${ANON["${r}"]}" - _rest "${ast}" - ${mac%%@*} ${ANON["${r}"]} - ast="${r}" - done - r="${ast}" -} - - -EVAL_AST () { - local ast="${1}" env="${2}" - #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" - _obj_type "${ast}"; local ot="${r}" - case "${ot}" in - symbol) - ENV_GET "${env}" "${ast}" - return ;; - list) - _map_with_type _list EVAL "${ast}" "${env}" ;; - vector) - _map_with_type _vector EVAL "${ast}" "${env}" ;; - hash_map) - local res="" val="" hm="${ANON["${ast}"]}" - _hash_map; local new_hm="${r}" - eval local keys="\${!${hm}[@]}" - for key in ${keys}; do - eval val="\${${hm}[\"${key}\"]}" - EVAL "${val}" "${env}" - _assoc! "${new_hm}" "${key}" "${r}" - done - r="${new_hm}" ;; - *) - r="${ast}" ;; - esac -} - -EVAL () { - local ast="${1}" env="${2}" - while true; do - r= - [[ "${__ERROR}" ]] && return 1 - #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" - if ! _list? "${ast}"; then - EVAL_AST "${ast}" "${env}" - return - fi - - # apply list - MACROEXPAND "${ast}" "${env}" - ast="${r}" - if ! _list? "${ast}"; then return; fi - _nth "${ast}" 0; local a0="${r}" - _nth "${ast}" 1; local a1="${r}" - _nth "${ast}" 2; local a2="${r}" - case "${ANON["${a0}"]}" in - 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}"]}) - local idx=0 - #echo "let: [${let_pairs[*]}] for ${a2}" - while [[ "${let_pairs["${idx}"]}" ]]; do - EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" - ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}" - idx=$(( idx + 2)) - done - ast="${a2}" - env="${let_env}" - # Continue loop - ;; - quote) - r="${a1}" - return ;; - quasiquote) - QUASIQUOTE "${a1}" - ast="${r}" - # Continue loop - ;; - defmacro!) - EVAL "${a2}" "${env}" - [[ "${__ERROR}" ]] && return 1 - ANON["${r}_ismacro_"]="yes" - ENV_SET "${env}" "${a1}" "${r}" - return ;; - macroexpand) - MACROEXPAND "${a1}" "${env}" - return ;; - sh*) EVAL "${a1}" "${env}" - local output="" - local line="" - while read line; do - output="${output}${line}\n" - done < <(eval ${ANON["${r}"]}) - _string "${output%\\n}" - return ;; - 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 )) - EVAL_AST "${r}" "${env}" - [[ "${__ERROR}" ]] && r= && return 1 - _last "${ast}" - ast="${r}" - # Continue loop - ;; - if) EVAL "${a1}" "${env}" - [[ "${__ERROR}" ]] && return 1 - if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then - # eval false form - _nth "${ast}" 3; local a3="${r}" - if [[ "${a3}" ]]; then - ast="${a3}" - else - r="${__nil}" - return - fi - else - # eval true condition - ast="${a2}" - fi - # Continue loop - ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ - EVAL \"${a2}\" \"\${r}\"" \ - "${a2}" "${env}" "${a1}" - return ;; - *) EVAL_AST "${ast}" "${env}" - [[ "${__ERROR}" ]] && r= && return 1 - local el="${r}" - _first "${el}"; local f="${ANON["${r}"]}" - _rest "${el}"; local args="${ANON["${r}"]}" - #echo "invoke: [${f}] ${args}" - if [[ "${f//@/ }" != "${f}" ]]; then - set -- ${f//@/ } - ast="${2}" - ENV "${3}" "${4}" ${args} - env="${r}" - else - eval ${f%%@*} ${args} - return - fi - # Continue loop - ;; - esac - done -} - -# print -PRINT () { - if [[ "${__ERROR}" ]]; then - _pr_str "${__ERROR}" yes - r="Error: ${r}" - __ERROR= - else - _pr_str "${1}" yes - fi -} - -# repl -ENV; REPL_ENV="${r}" -REP () { - r= - READ "${1}" - EVAL "${r}" "${REPL_ENV}" - PRINT "${r}" -} - -# core.sh: defined using bash -_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 -_symbol "__STAR__ARGV__STAR__" -ENV_SET "${REPL_ENV}" "${r}" "${argv}"; - -# core.mal: defined using the language itself -REP "(def! *host-language* \"bash\")" -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))))))))" - -# load/run file from command line (then exit) -if [[ "${1}" ]]; then - REP "(load-file \"${1}\")" - exit 0 -fi - -# repl loop -REP "(println (str \"Mal [\" *host-language* \"]\"))" -while true; do - READLINE "user> " || exit "$?" - [[ "${r}" ]] && REP "${r}" && echo "${r}" -done diff --git a/bash/stepA_mal.sh b/bash/stepA_mal.sh new file mode 100755 index 0000000..7b43496 --- /dev/null +++ b/bash/stepA_mal.sh @@ -0,0 +1,283 @@ +#!/usr/bin/env bash + +source $(dirname $0)/reader.sh +source $(dirname $0)/printer.sh +source $(dirname $0)/env.sh +source $(dirname $0)/core.sh + +# read +READ () { + [ "${1}" ] && r="${1}" || READLINE + READ_STR "${r}" +} + +# eval +IS_PAIR () { + if _sequential? "${1}"; then + _count "${1}" + [[ "${r}" > 0 ]] && return 0 + fi + return 1 +} + +QUASIQUOTE () { + if ! IS_PAIR "${1}"; then + _symbol quote + _list "${r}" "${1}" + return + else + _nth "${1}" 0; local a0="${r}" + if [[ "${ANON["${a0}"]}" == "unquote" ]]; then + _nth "${1}" 1 + return + elif IS_PAIR "${a0}"; then + _nth "${a0}" 0; local a00="${r}" + if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then + _symbol concat; local a="${r}" + _nth "${a0}" 1; local b="${r}" + _rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + _list "${a}" "${b}" "${c}" + return + fi + fi + fi + _symbol cons; local a="${r}" + QUASIQUOTE "${a0}"; local b="${r}" + _rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + _list "${a}" "${b}" "${c}" + return +} + +IS_MACRO_CALL () { + if ! _list? "${1}"; then return 1; fi + _nth "${1}" 0; local a0="${r}" + if _symbol? "${a0}"; then + ENV_FIND "${2}" "${a0}" + if [[ "${r}" ]]; then + ENV_GET "${2}" "${a0}" + [ "${ANON["${r}_ismacro_"]}" ] + return $? + fi + fi + return 1 +} + +MACROEXPAND () { + local ast="${1}" env="${2}" + while IS_MACRO_CALL "${ast}" "${env}"; do + _nth "${ast}" 0; local a0="${r}" + ENV_GET "${env}" "${a0}"; local mac="${ANON["${r}"]}" + _rest "${ast}" + ${mac%%@*} ${ANON["${r}"]} + ast="${r}" + done + r="${ast}" +} + + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + ENV_GET "${env}" "${ast}" + return ;; + list) + _map_with_type _list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type _vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + _hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + _assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + if ! _list? "${ast}"; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + MACROEXPAND "${ast}" "${env}" + ast="${r}" + if ! _list? "${ast}"; then return; fi + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + 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}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}" + idx=$(( idx + 2)) + done + ast="${a2}" + env="${let_env}" + # Continue loop + ;; + quote) + r="${a1}" + return ;; + quasiquote) + QUASIQUOTE "${a1}" + ast="${r}" + # Continue loop + ;; + defmacro!) + EVAL "${a2}" "${env}" + [[ "${__ERROR}" ]] && return 1 + ANON["${r}_ismacro_"]="yes" + ENV_SET "${env}" "${a1}" "${r}" + return ;; + macroexpand) + MACROEXPAND "${a1}" "${env}" + return ;; + sh*) EVAL "${a1}" "${env}" + local output="" + local line="" + while read line; do + output="${output}${line}\n" + done < <(eval ${ANON["${r}"]}) + _string "${output%\\n}" + return ;; + 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 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + _last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + [[ "${__ERROR}" ]] && return 1 + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + _first "${el}"; local f="${ANON["${r}"]}" + _rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} + +# print +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# repl +ENV; REPL_ENV="${r}" +REP () { + r= + READ "${1}" + EVAL "${r}" "${REPL_ENV}" + PRINT "${r}" +} + +# core.sh: defined using bash +_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 +_symbol "__STAR__ARGV__STAR__" +ENV_SET "${REPL_ENV}" "${r}" "${argv}"; + +# core.mal: defined using the language itself +REP "(def! *host-language* \"bash\")" +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))))))))" + +# load/run file from command line (then exit) +if [[ "${1}" ]]; then + REP "(load-file \"${1}\")" + exit 0 +fi + +# repl loop +REP "(println (str \"Mal [\" *host-language* \"]\"))" +while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" +done diff --git a/bash/tests/stepA_interop.mal b/bash/tests/stepA_interop.mal deleted file mode 100644 index bf3eabd..0000000 --- a/bash/tests/stepA_interop.mal +++ /dev/null @@ -1,17 +0,0 @@ -;; Testing basic bash interop - -(sh* "echo 7") -;=>"7" - -(sh* "echo >&2 hello") -; hello -;=>"" - -(sh* "foo=8; echo ${foo}") -;=>"8" - -(sh* "for x in a b c; do echo -n \"X${x}Y \"; done; echo") -;=>"XaY XbY XcY" - -(sh* "for x in 1 2 3; do echo -n \"$((1+$x)) \"; done; echo") -;=>"2 3 4" diff --git a/bash/tests/stepA_mal.mal b/bash/tests/stepA_mal.mal new file mode 100644 index 0000000..bf3eabd --- /dev/null +++ b/bash/tests/stepA_mal.mal @@ -0,0 +1,17 @@ +;; Testing basic bash interop + +(sh* "echo 7") +;=>"7" + +(sh* "echo >&2 hello") +; hello +;=>"" + +(sh* "foo=8; echo ${foo}") +;=>"8" + +(sh* "for x in a b c; do echo -n \"X${x}Y \"; done; echo") +;=>"XaY XbY XcY" + +(sh* "for x in 1 2 3; do echo -n \"$((1+$x)) \"; done; echo") +;=>"2 3 4" diff --git a/c/Makefile b/c/Makefile index 81b384c..e51e04b 100644 --- a/c/Makefile +++ b/c/Makefile @@ -9,7 +9,7 @@ TESTS = SOURCES_BASE = readline.h readline.c types.h types.c \ reader.h reader.c printer.h printer.c \ interop.h interop.c -SOURCES_LISP = env.c core.h core.c stepA_interop.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_try.c stepA_interop.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 diff --git a/c/stepA_interop.c b/c/stepA_interop.c deleted file mode 100644 index 05e9f65..0000000 --- a/c/stepA_interop.c +++ /dev/null @@ -1,356 +0,0 @@ -#include -#include -#include - -#include "types.h" -#include "readline.h" -#include "reader.h" -#include "core.h" -#include "interop.h" - -// Declarations -MalVal *EVAL(MalVal *ast, Env *env); -MalVal *macroexpand(MalVal *ast, Env *env); - -// read -MalVal *READ(char prompt[], char *str) { - char *line; - MalVal *ast; - if (str) { - line = str; - } else { - line = _readline(prompt); - if (!line) { - _error("EOF"); - return NULL; - } - } - ast = read_str(line); - if (!str) { free(line); } - return ast; -} - -// eval -int is_pair(MalVal *x) { - return _sequential_Q(x) && (_count(x) > 0); -} - -MalVal *quasiquote(MalVal *ast) { - if (!is_pair(ast)) { - return _listX(2, malval_new_symbol("quote"), ast); - } else { - MalVal *a0 = _nth(ast, 0); - if ((a0->type & MAL_SYMBOL) && - strcmp("unquote", a0->val.string) == 0) { - return _nth(ast, 1); - } else if (is_pair(a0)) { - MalVal *a00 = _nth(a0, 0); - if ((a00->type & MAL_SYMBOL) && - strcmp("splice-unquote", a00->val.string) == 0) { - return _listX(3, malval_new_symbol("concat"), - _nth(a0, 1), - quasiquote(_rest(ast))); - } - } - return _listX(3, malval_new_symbol("cons"), - quasiquote(a0), - quasiquote(_rest(ast))); - } -} - -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) && - 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); - // TODO: this is weird and limits it to 20. FIXME - ast = _apply(mac, _rest(ast)); - } - return ast; -} - -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); - } 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); - if (!el || mal_error) return NULL; - el->type = ast->type; - return el; - } else if (ast->type == MAL_HASH_MAP) { - //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); - GHashTableIter iter; - gpointer key, value; - MalVal *seq = malval_new_list(MAL_LIST, - g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), - _count(ast))); - g_hash_table_iter_init (&iter, ast->val.hash_table); - while (g_hash_table_iter_next (&iter, &key, &value)) { - MalVal *kname = malval_new_string((char *)key); - g_array_append_val(seq->val.array, kname); - MalVal *new_val = EVAL((MalVal *)value, env); - g_array_append_val(seq->val.array, new_val); - } - return _hash_map(seq); - } else { - //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); - return ast; - } -} - -MalVal *EVAL(MalVal *ast, Env *env) { - while (TRUE) { - - if (!ast || mal_error) return NULL; - //g_print("EVAL: %s\n", _pr_str(ast,1)); - if (ast->type != MAL_LIST) { - return eval_ast(ast, env); - } - if (!ast || mal_error) return NULL; - - // apply list - //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); - ast = macroexpand(ast, env); - if (!ast || mal_error) return NULL; - if (ast->type != MAL_LIST) { return ast; } - if (_count(ast) == 0) { return ast; } - - int i, len; - MalVal *a0 = _nth(ast, 0); - if ((a0->type & MAL_SYMBOL) && - strcmp("def!", a0->val.string) == 0) { - //g_print("eval apply def!\n"); - MalVal *a1 = _nth(ast, 1), - *a2 = _nth(ast, 2); - MalVal *res = EVAL(a2, env); - if (mal_error) return NULL; - env_set(env, a1, res); - return res; - } else if ((a0->type & MAL_SYMBOL) && - strcmp("let*", a0->val.string) == 0) { - //g_print("eval apply let*\n"); - MalVal *a1 = _nth(ast, 1), - *a2 = _nth(ast, 2), - *key, *val; - assert_type(a1, MAL_LIST|MAL_VECTOR, - "let* bindings must be list or vector"); - len = _count(a1); - assert((len % 2) == 0, "odd number of let* bindings forms"); - Env *let_env = new_env(env, NULL, NULL); - for(i=0; ival.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, EVAL(val, let_env)); - } - ast = a2; - env = let_env; - // Continue loop - } else if ((a0->type & MAL_SYMBOL) && - strcmp("quote", a0->val.string) == 0) { - //g_print("eval apply quote\n"); - return _nth(ast, 1); - } else if ((a0->type & MAL_SYMBOL) && - strcmp("quasiquote", a0->val.string) == 0) { - //g_print("eval apply quasiquote\n"); - MalVal *a1 = _nth(ast, 1); - ast = quasiquote(a1); - // Continue loop - } else if ((a0->type & MAL_SYMBOL) && - strcmp("defmacro!", a0->val.string) == 0) { - //g_print("eval apply defmacro!\n"); - 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, res); - return res; - } else if ((a0->type & MAL_SYMBOL) && - strcmp("macroexpand", a0->val.string) == 0) { - //g_print("eval apply macroexpand\n"); - 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); - } else if ((a0->type & MAL_SYMBOL) && - 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"); - eval_ast(_slice(ast, 1, _count(ast)-1), env); - ast = _last(ast); - // Continue loop - } else if ((a0->type & MAL_SYMBOL) && - strcmp("if", a0->val.string) == 0) { - //g_print("eval apply if\n"); - MalVal *a1 = _nth(ast, 1); - MalVal *cond = EVAL(a1, env); - if (!cond || mal_error) return NULL; - if (cond->type & (MAL_FALSE|MAL_NIL)) { - // eval false slot form - if (ast->val.array->len > 3) { - ast = _nth(ast, 3); - } else { - return &mal_nil; - } - } else { - // eval true slot form - ast = _nth(ast, 2); - } - // Continue loop - } else if ((a0->type & MAL_SYMBOL) && - strcmp("fn*", a0->val.string) == 0) { - //g_print("eval apply fn*\n"); - MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); - mf->ismacro = FALSE; - mf->val.func.evaluator = EVAL; - mf->val.func.args = _nth(ast, 1); - mf->val.func.body = _nth(ast, 2); - mf->val.func.env = env; - return mf; - } else { - //g_print("eval apply\n"); - MalVal *el = eval_ast(ast, env); - if (!el || mal_error) { return NULL; } - MalVal *f = _first(el), - *args = _rest(el); - assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, - "cannot apply '%s'", _pr_str(f,1)); - if (f->type & MAL_FUNCTION_MAL) { - ast = f->val.func.body; - env = new_env(f->val.func.env, f->val.func.args, args); - // Continue loop - } else { - return _apply(f, args); - } - } - - } // TCO while loop -} - -// print -char *PRINT(MalVal *exp) { - if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; - return NULL; - } - return _pr_str(exp,1); -} - -// repl - -// read and eval -MalVal *RE(Env *env, char *prompt, char *str) { - MalVal *ast, *exp; - ast = READ(prompt, str); - if (!ast || mal_error) return NULL; - exp = EVAL(ast, env); - if (ast != exp) { - malval_free(ast); // Free input structure - } - return exp; -} - -// Setup the initial REPL environment -Env *repl_env; - -void init_repl_env(int argc, char *argv[]) { - repl_env = new_env(NULL, NULL, NULL); - - // core.c: defined using C - int i; - for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) { - 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, - malval_new_symbol("eval"), - malval_new_function((void*(*)(void *))do_eval, 1)); - - MalVal *_argv = _listX(0); - for (i=2; i < argc; i++) { - MalVal *arg = malval_new_string(argv[i]); - g_array_append_val(_argv->val.array, arg); - } - env_set(repl_env, malval_new_symbol("*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))))))))"); -} - -int main(int argc, char *argv[]) -{ - MalVal *exp; - char *output; - char prompt[100]; - - // Set the initial prompt and environment - snprintf(prompt, sizeof(prompt), "user> "); - init_repl_env(argc, argv); - - if (argc > 1) { - char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); - RE(repl_env, "", cmd); - return 0; - } - - // repl loop - RE(repl_env, "", "(println (str \"Mal [\" *host-language* \"]\"))"); - for(;;) { - exp = RE(repl_env, prompt, NULL); - if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { - return 0; - } - output = PRINT(exp); - - if (output) { - g_print("%s\n", output); - free(output); // Free output string - } - - //malval_free(exp); // Free evaluated expression - } -} diff --git a/c/stepA_mal.c b/c/stepA_mal.c new file mode 100644 index 0000000..05e9f65 --- /dev/null +++ b/c/stepA_mal.c @@ -0,0 +1,356 @@ +#include +#include +#include + +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "core.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _listX(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _listX(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(_rest(ast))); + } + } + return _listX(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(_rest(ast))); + } +} + +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) && + 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); + // TODO: this is weird and limits it to 20. FIXME + ast = _apply(mac, _rest(ast)); + } + return ast; +} + +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); + } 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); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return _hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + if (mal_error) return NULL; + env_set(env, a1, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; ival.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, EVAL(val, let_env)); + } + ast = a2; + env = let_env; + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + ast = quasiquote(a1); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + 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, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + 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); + } else if ((a0->type & MAL_SYMBOL) && + 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"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = _last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + if (ast->val.array->len > 3) { + ast = _nth(ast, 3); + } else { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = _first(el), + *args = _rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot apply '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return _apply(f, args); + } + } + + } // TCO while loop +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env(int argc, char *argv[]) { + repl_env = new_env(NULL, NULL, NULL); + + // core.c: defined using C + int i; + for(i=0; i < (sizeof(core_ns) / sizeof(core_ns[0])); i++) { + 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, + malval_new_symbol("eval"), + malval_new_function((void*(*)(void *))do_eval, 1)); + + MalVal *_argv = _listX(0); + for (i=2; i < argc; i++) { + MalVal *arg = malval_new_string(argv[i]); + g_array_append_val(_argv->val.array, arg); + } + env_set(repl_env, malval_new_symbol("*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))))))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(argc, argv); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + return 0; + } + + // repl loop + RE(repl_env, "", "(println (str \"Mal [\" *host-language* \"]\"))"); + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/tests/stepA_interop.mal b/c/tests/stepA_interop.mal deleted file mode 100644 index 657e3e7..0000000 --- a/c/tests/stepA_interop.mal +++ /dev/null @@ -1,23 +0,0 @@ - -;; Testing FFI of "strlen" -(. nil "int32" "strlen" "string" "abcde") -;=>5 -(. nil "int32" "strlen" "string" "") -;=>0 - -;; Testing FFI of "strcmp" - -(. nil "int32" "strcmp" "string" "abc" "string" "abcA") -;=>-65 -(. nil "int32" "strcmp" "string" "abcA" "string" "abc") -;=>65 -(. nil "int32" "strcmp" "string" "abc" "string" "abc") -;=>0 - - -;; Testing FFI of "pow" (libm.so) - -(. "libm.so" "double" "pow" "double" 2.0 "double" 3.0) -;=>8.000000 -(. "libm.so" "double" "pow" "double" 3.0 "double" 2.0) -;=>9.000000 diff --git a/c/tests/stepA_mal.mal b/c/tests/stepA_mal.mal new file mode 100644 index 0000000..657e3e7 --- /dev/null +++ b/c/tests/stepA_mal.mal @@ -0,0 +1,23 @@ + +;; Testing FFI of "strlen" +(. nil "int32" "strlen" "string" "abcde") +;=>5 +(. nil "int32" "strlen" "string" "") +;=>0 + +;; Testing FFI of "strcmp" + +(. nil "int32" "strcmp" "string" "abc" "string" "abcA") +;=>-65 +(. nil "int32" "strcmp" "string" "abcA" "string" "abc") +;=>65 +(. nil "int32" "strcmp" "string" "abc" "string" "abc") +;=>0 + + +;; Testing FFI of "pow" (libm.so) + +(. "libm.so" "double" "pow" "double" 2.0 "double" 3.0) +;=>8.000000 +(. "libm.so" "double" "pow" "double" 3.0 "double" 2.0) +;=>9.000000 diff --git a/clojure/Makefile b/clojure/Makefile index 6d227a2..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_interop.clj +SOURCES_LISP = src/env.clj src/core.clj src/stepA_mal.clj SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) all: diff --git a/clojure/src/stepA_interop.clj b/clojure/src/stepA_interop.clj deleted file mode 100644 index 6ed9964..0000000 --- a/clojure/src/stepA_interop.clj +++ /dev/null @@ -1,181 +0,0 @@ -(ns stepA-interop - (:refer-clojure :exclude [macroexpand]) - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core])) - -;; read -(defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) - -;; eval -(declare EVAL) -(defn is-pair [x] - (and (sequential? x) (> (count x) 0))) - -(defn quasiquote [ast] - (cond - (not (is-pair ast)) - (list 'quote ast) - - (= 'unquote (first ast)) - (second ast) - - (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast))) - (list 'concat (-> ast first second) (quasiquote (rest ast))) - - :else - (list 'cons (quasiquote (first ast)) (quasiquote (rest ast))))) - -(defn is-macro-call [ast env] - (and (seq? ast) - (symbol? (first ast)) - (env/env-find env (first ast)) - (:ismacro (meta (env/env-get env (first ast)))))) - -(defn macroexpand [ast env] - (loop [ast ast] - (if (is-macro-call ast env) - (let [mac (env/env-get env (first ast))] - (recur (apply mac (rest ast)))) - ast))) - -(defn eval-ast [ast env] - (cond - (symbol? ast) (env/env-get env ast) - - (seq? ast) (doall (map #(EVAL % env) ast)) - - (vector? ast) (vec (doall (map #(EVAL % env) ast))) - - (map? ast) (apply hash-map (doall (map #(EVAL % env) - (mapcat identity ast)))) - - :else ast)) - -(defn EVAL [ast env] - (loop [ast ast - env env] - ;;(prn "EVAL" ast (keys @env)) (flush) - (if (not (seq? ast)) - (eval-ast ast env) - - ;; apply list - (let [ast (macroexpand ast env)] - (if (not (seq? ast)) - ast - - (let [[a0 a1 a2 a3] ast] - (condp = a0 - 'def! - (env/env-set env a1 (EVAL a2 env)) - - 'let* - (let [let-env (env/env env)] - (doseq [[b e] (partition 2 a1)] - (env/env-set let-env b (EVAL e let-env))) - (recur a2 let-env)) - - 'quote - a1 - - 'quasiquote - (recur (quasiquote a1) env) - - 'defmacro! - (let [func (with-meta (EVAL a2 env) - {:ismacro true})] - (env/env-set env a1 func)) - - '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)) - - 'if - (let [cond (EVAL a1 env)] - (if (or (= cond nil) (= cond false)) - (if (> (count ast) 2) - (recur a3 env) - nil) - (recur a2 env))) - - 'fn* - (with-meta - (fn [& args] - (EVAL a2 (env/env env a1 args))) - {:expression a2 - :environment env - :parameters a1}) - - ;; apply - (let [el (eval-ast ast env) - f (first el) - args (rest el) - {:keys [expression environment parameters]} (meta f)] - (if expression - (recur expression (env/env environment parameters args)) - (apply f args)))))))))) - -;; print -(defn PRINT [exp] (pr-str exp)) - -;; repl -(def repl-env (env/env)) -(defn rep - [strng] - (PRINT (EVAL (READ strng) repl-env))) - -;; core.clj: defined using Clojure -(doseq [[k v] core/core_ns] (env/env-set repl-env k v)) -(env/env-set repl-env 'eval (fn [ast] (EVAL ast repl-env))) -(env/env-set repl-env '*ARGV* ()) - -;; core.mal: defined using the language itself -(rep "(def! *host-language* \"clojure\")") -(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 -(defn repl-loop [] - (let [line (readline/readline "user> ")] - (when line - (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment - (try - (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) - (recur)))) - -(defn -main [& args] - (env/env-set repl-env '*ARGV* (rest args)) - (if args - (rep (str "(load-file \"" (first args) "\")")) - (do - (rep "(println (str \"Mal [\" *host-language* \"]\"))") - (repl-loop)))) diff --git a/clojure/src/stepA_mal.clj b/clojure/src/stepA_mal.clj new file mode 100644 index 0000000..6ed9964 --- /dev/null +++ b/clojure/src/stepA_mal.clj @@ -0,0 +1,181 @@ +(ns stepA-interop + (:refer-clojure :exclude [macroexpand]) + (:require [clojure.repl] + [readline] + [reader] + [printer] + [env] + [core])) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(declare EVAL) +(defn is-pair [x] + (and (sequential? x) (> (count x) 0))) + +(defn quasiquote [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (second ast) + + (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast))) + (list 'concat (-> ast first second) (quasiquote (rest ast))) + + :else + (list 'cons (quasiquote (first ast)) (quasiquote (rest ast))))) + +(defn is-macro-call [ast env] + (and (seq? ast) + (symbol? (first ast)) + (env/env-find env (first ast)) + (:ismacro (meta (env/env-get env (first ast)))))) + +(defn macroexpand [ast env] + (loop [ast ast] + (if (is-macro-call ast env) + (let [mac (env/env-get env (first ast))] + (recur (apply mac (rest ast)))) + ast))) + +(defn eval-ast [ast env] + (cond + (symbol? ast) (env/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [ast (macroexpand ast env)] + (if (not (seq? ast)) + ast + + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (env/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (env/env env)] + (doseq [[b e] (partition 2 a1)] + (env/env-set let-env b (EVAL e let-env))) + (recur a2 let-env)) + + 'quote + a1 + + 'quasiquote + (recur (quasiquote a1) env) + + 'defmacro! + (let [func (with-meta (EVAL a2 env) + {:ismacro true})] + (env/env-set env a1 func)) + + '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)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + (with-meta + (fn [& args] + (EVAL a2 (env/env env a1 args))) + {:expression a2 + :environment env + :parameters a1}) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (env/env environment parameters args)) + (apply f args)))))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (env/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng) repl-env))) + +;; core.clj: defined using Clojure +(doseq [[k v] core/core_ns] (env/env-set repl-env k v)) +(env/env-set repl-env 'eval (fn [ast] (EVAL ast repl-env))) +(env/env-set repl-env '*ARGV* ()) + +;; core.mal: defined using the language itself +(rep "(def! *host-language* \"clojure\")") +(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 +(defn repl-loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur)))) + +(defn -main [& args] + (env/env-set repl-env '*ARGV* (rest args)) + (if args + (rep (str "(load-file \"" (first args) "\")")) + (do + (rep "(println (str \"Mal [\" *host-language* \"]\"))") + (repl-loop)))) diff --git a/clojure/tests/stepA_interop.mal b/clojure/tests/stepA_interop.mal deleted file mode 100644 index b323222..0000000 --- a/clojure/tests/stepA_interop.mal +++ /dev/null @@ -1,17 +0,0 @@ -;; Testing basic clojure interop - -(clj* "7") -;=>7 - -(clj* "\"abc\"") -;=>"abc" - -(clj* "{\"abc\" 123}") -;=>{"abc" 123} - -(clj* "(prn \"foo\")") -; "foo" -;=>nil - -(clj* "(for [x [1 2 3]] (+ 1 x))") -;=>(2 3 4) diff --git a/clojure/tests/stepA_mal.mal b/clojure/tests/stepA_mal.mal new file mode 100644 index 0000000..b323222 --- /dev/null +++ b/clojure/tests/stepA_mal.mal @@ -0,0 +1,17 @@ +;; Testing basic clojure interop + +(clj* "7") +;=>7 + +(clj* "\"abc\"") +;=>"abc" + +(clj* "{\"abc\" 123}") +;=>{"abc" 123} + +(clj* "(prn \"foo\")") +; "foo" +;=>nil + +(clj* "(for [x [1 2 3]] (+ 1 x))") +;=>(2 3 4) diff --git a/coffee/Makefile b/coffee/Makefile index d2212ed..728d9f2 100644 --- a/coffee/Makefile +++ b/coffee/Makefile @@ -2,7 +2,7 @@ TESTS = SOURCES_BASE = node_readline.coffee types.coffee \ reader.coffee printer.coffee -SOURCES_LISP = env.coffee core.coffee stepA_interop.coffee +SOURCES_LISP = env.coffee core.coffee stepA_mal.coffee SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) #all: mal.rb diff --git a/coffee/stepA_interop.coffee b/coffee/stepA_interop.coffee deleted file mode 100644 index 751f9ad..0000000 --- a/coffee/stepA_interop.coffee +++ /dev/null @@ -1,142 +0,0 @@ -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/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/coffee/tests/stepA_interop.mal b/coffee/tests/stepA_interop.mal deleted file mode 100644 index f785292..0000000 --- a/coffee/tests/stepA_interop.mal +++ /dev/null @@ -1,24 +0,0 @@ -;; 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/coffee/tests/stepA_mal.mal b/coffee/tests/stepA_mal.mal new file mode 100644 index 0000000..f785292 --- /dev/null +++ b/coffee/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/cs/Makefile b/cs/Makefile index 1fd1f7a..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_interop.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 step9_try.cs stepA_interop.cs + step8_macros.cs step9_try.cs stepA_mal.cs LIB_SRCS = $(filter-out step%,$(OTHER_SOURCES) $(SOURCES)) diff --git a/cs/stepA_interop.cs b/cs/stepA_interop.cs deleted file mode 100644 index 6531d50..0000000 --- a/cs/stepA_interop.cs +++ /dev/null @@ -1,285 +0,0 @@ -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 stepA_interop { - // 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(); - 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 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 = 0; - if (args.Length > 0 && args[0] == "--raw") { - Mal.readline.mode = Mal.readline.Mode.Raw; - fileIdx = 1; - } - 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! *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))))))))"); - - if (args.Length > fileIdx) { - RE("(load-file \"" + args[fileIdx] + "\")"); - return; - } - - // repl loop - RE("(println (str \"Mal [\" *host-language* \"]\"))"); - 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_mal.cs b/cs/stepA_mal.cs new file mode 100644 index 0000000..0ccb39e --- /dev/null +++ b/cs/stepA_mal.cs @@ -0,0 +1,285 @@ +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 stepA_mal { + // 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(); + 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 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 = 0; + if (args.Length > 0 && args[0] == "--raw") { + Mal.readline.mode = Mal.readline.Mode.Raw; + fileIdx = 1; + } + 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! *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))))))))"); + + if (args.Length > fileIdx) { + RE("(load-file \"" + args[fileIdx] + "\")"); + return; + } + + // repl loop + RE("(println (str \"Mal [\" *host-language* \"]\"))"); + 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/docs/TODO b/docs/TODO index e5b135b..e5a4fc1 100644 --- a/docs/TODO +++ b/docs/TODO @@ -1,7 +1,6 @@ All: - Finish guide.md - - rename stepA_interop to stepA_mal - test to check args set properly - test to make sure slurp captures final newline - make sure errors propagate/print properly when self-hosted @@ -94,7 +93,7 @@ Lua: Make: - allow '_' in make variable names - hash-map with space in key string - - Fix: make -f stepA_interop.mk ../mal/step6_file.mal + - 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 @@ -121,7 +120,7 @@ Postscript: - formatting messed up with mal/clojurewest2014.mal Python: - - error: python ../python/stepA_interop.py ../mal/stepA_interop.mal ../mal/stepA_interop.mal + - error: python ../python/stepA_mal.py ../mal/stepA_mal.mal ../mal/stepA_mal.mal - interop tests R: diff --git a/docs/step_notes.txt b/docs/step_notes.txt index f36575d..e28761a 100644 --- a/docs/step_notes.txt +++ b/docs/step_notes.txt @@ -405,7 +405,7 @@ Step Notes: - Other misc: - conj function -- stepA_interop +- stepA_mal - convert returned data to mal data - recursive, similar to pr_str - Details: diff --git a/forth/Makefile b/forth/Makefile index 400b463..29bf799 100644 --- a/forth/Makefile +++ b/forth/Makefile @@ -1,5 +1,5 @@ SOURCES_BASE = types.fs str.fs reader.fs printer.fs -SOURCES_LISP = env.fs core.fs stepA_interop.fs +SOURCES_LISP = env.fs core.fs stepA_mal.fs SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) .PHONY: stats tests $(TESTS) diff --git a/forth/stepA_interop.fs b/forth/stepA_interop.fs deleted file mode 100644 index af5f5d8..0000000 --- a/forth/stepA_interop.fs +++ /dev/null @@ -1,390 +0,0 @@ -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/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/tests/stepA_interop.mal b/forth/tests/stepA_interop.mal deleted file mode 100644 index c4a0e75..0000000 --- a/forth/tests/stepA_interop.mal +++ /dev/null @@ -1,41 +0,0 @@ -;; 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/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/go/Makefile b/go/Makefile index 5c966f5..67e967b 100644 --- a/go/Makefile +++ b/go/Makefile @@ -6,14 +6,14 @@ 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_interop/stepA_interop.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_interop.go + step8_macros.go step9_try.go stepA_mal.go BINS = $(SRCS:%.go=%) ##################### diff --git a/go/src/stepA_interop/stepA_interop.go b/go/src/stepA_interop/stepA_interop.go deleted file mode 100644 index 808022a..0000000 --- a/go/src/stepA_interop/stepA_interop.go +++ /dev/null @@ -1,306 +0,0 @@ -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() == "" { 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() == "" { continue } + fmt.Printf("Error: %v\n", e) + continue + } + fmt.Printf("%v\n", out) + } +} diff --git a/haskell/Makefile b/haskell/Makefile index 28c3d26..0ac1a75 100644 --- a/haskell/Makefile +++ b/haskell/Makefile @@ -6,7 +6,7 @@ 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_interop.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=%) diff --git a/haskell/stepA_interop.hs b/haskell/stepA_interop.hs deleted file mode 100644 index f1d4b38..0000000 --- a/haskell/stepA_interop.hs +++ /dev/null @@ -1,255 +0,0 @@ -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/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 eff6e6f..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_interop.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 ae53194..fa2b567 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -47,7 +47,7 @@