├── .gitmodules ├── tests ├── test.txt ├── incA.mal ├── print_argv.mal ├── inc.mal ├── incC.mal ├── docker-build.sh ├── incB.mal ├── docker-run.sh ├── perf2.mal ├── perf1.mal ├── step5_tco.mal ├── step0_repl.mal ├── perf3.mal ├── step2_eval.mal ├── step3_env.mal ├── step6_file.mal ├── step1_read_print.mal ├── step8_macros.mal ├── step7_quote.mal ├── docker │ └── Dockerfile ├── stepA_mal.mal ├── step9_try.mal └── step4_if_fn_do.mal ├── examples ├── hello.mal ├── memoize.mal ├── pprint.mal ├── protocols.mal ├── equality.mal ├── presentation.mal └── clojurewest2014.mal ├── rust ├── run ├── Cargo.toml ├── src │ ├── lib.rs │ ├── util.rs │ ├── bin │ │ ├── step0_repl.rs │ │ ├── step1_read_print.rs │ │ ├── step2_eval.rs │ │ ├── step3_env.rs │ │ ├── step5_tco.rs │ │ ├── step4_if_fn_do.rs │ │ └── step6_file.rs │ ├── readline.rs │ ├── env.rs │ ├── printer.rs │ └── reader.rs └── Makefile ├── mal ├── run ├── step0_repl.mal ├── step1_read_print.mal ├── Makefile ├── env.mal ├── Dockerfile ├── core.mal ├── step2_eval.mal ├── step3_env.mal ├── step4_if_fn_do.mal ├── step6_file.mal ├── step7_quote.mal ├── step8_macros.mal ├── step9_try.mal └── stepA_mal.mal ├── README.md ├── .travis_test.sh ├── perf.mal ├── run_argv_test.sh ├── .travis_build.sh ├── core.mal ├── .gitignore ├── docs ├── TODO ├── Hints.md └── FAQ.md ├── runtest-old.py ├── .travis.yml └── mal.html /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test.txt: -------------------------------------------------------------------------------- 1 | A line of text 2 | -------------------------------------------------------------------------------- /tests/incA.mal: -------------------------------------------------------------------------------- 1 | (def! inc4 (fn* (a) (+ 4 a))) 2 | 3 | (prn (inc4 5)) 4 | -------------------------------------------------------------------------------- /tests/print_argv.mal: -------------------------------------------------------------------------------- 1 | ; Used by the run_argv_test.sh test harness 2 | (prn *ARGV*) 3 | -------------------------------------------------------------------------------- /examples/hello.mal: -------------------------------------------------------------------------------- 1 | (println "hello world\n\nanother line") 2 | (println "and another line") 3 | -------------------------------------------------------------------------------- /rust/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export READLINE=false 3 | exec $(dirname $0)/target/release/${STEP:-stepA_mal} "${@}" 4 | -------------------------------------------------------------------------------- /tests/inc.mal: -------------------------------------------------------------------------------- 1 | (def! inc1 (fn* (a) (+ 1 a))) 2 | (def! inc2 (fn* (a) (+ 2 a))) 3 | (def! inc3 (fn* (a) 4 | (+ 3 a))) 5 | -------------------------------------------------------------------------------- /tests/incC.mal: -------------------------------------------------------------------------------- 1 | (def! mymap {"a" 2 | 1}) 3 | 4 | (prn "incC.mal finished") 5 | "incC.mal return string" 6 | 7 | -------------------------------------------------------------------------------- /mal/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd $(dirname $0) 3 | MAL_FILE=./../mal/${STEP:-stepA_mal}.mal 4 | export STEP=stepA_mal # force MAL_IMPL to use stepA 5 | exec ./../${MAL_IMPL:-js}/run ${MAL_FILE} "${@}" 6 | -------------------------------------------------------------------------------- /tests/docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE_NAME=${IMAGE_NAME:-mal-test-ubuntu-utopic} 4 | GIT_TOP=$(git rev-parse --show-toplevel) 5 | 6 | docker build -t "${IMAGE_NAME}" "${GIT_TOP}/tests/docker" 7 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mal-rust" 3 | version = "0.1.0" 4 | authors = ["Tim Morgan "] 5 | 6 | [dependencies] 7 | linefeed = "0.5.0" 8 | regex = "0.2" 9 | lazy_static = "1.0" 10 | time = "0.1.40" 11 | -------------------------------------------------------------------------------- /tests/incB.mal: -------------------------------------------------------------------------------- 1 | ;; A comment in a file 2 | (def! inc4 (fn* (a) (+ 4 a))) 3 | (def! inc5 (fn* (a) ;; a comment after code 4 | (+ 5 a))) 5 | 6 | (prn "incB.mal finished") 7 | "incB.mal return string" 8 | 9 | ;; ending comment 10 | 11 | -------------------------------------------------------------------------------- /tests/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE_NAME=${IMAGE_NAME:-mal-test-ubuntu-utopic} 4 | GIT_TOP=$(git rev-parse --show-toplevel) 5 | 6 | docker run -it --rm -u ${EUID} \ 7 | --volume=${GIT_TOP}:/mal \ 8 | ${IMAGE_NAME} \ 9 | "${@}" 10 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | extern crate linefeed; 5 | extern crate regex; 6 | extern crate time; 7 | 8 | pub mod core; 9 | pub mod env; 10 | pub mod printer; 11 | pub mod reader; 12 | pub mod readline; 13 | pub mod types; 14 | pub mod util; 15 | -------------------------------------------------------------------------------- /tests/perf2.mal: -------------------------------------------------------------------------------- 1 | (load-file "../core.mal") 2 | (load-file "../perf.mal") 3 | 4 | ;;(prn "Start: basic math/recursion test") 5 | 6 | (def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) 7 | (def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2))))))) 8 | 9 | (time (do 10 | (sumdown 10) 11 | (fib 12))) 12 | 13 | ;;(prn "Done: basic math/recursion test") 14 | -------------------------------------------------------------------------------- /tests/perf1.mal: -------------------------------------------------------------------------------- 1 | (load-file "../core.mal") 2 | (load-file "../perf.mal") 3 | 4 | ;;(prn "Start: basic macros performance test") 5 | 6 | (time (do 7 | (or false nil false nil false nil false nil false nil 4) 8 | (cond false 1 nil 2 false 3 nil 4 false 5 nil 6 "else" 7) 9 | (-> (list 1 2 3 4 5 6 7 8 9) rest rest rest rest rest rest first))) 10 | 11 | ;;(prn "Done: basic macros performance test") 12 | -------------------------------------------------------------------------------- /tests/step5_tco.mal: -------------------------------------------------------------------------------- 1 | ;; Testing recursive tail-call function 2 | 3 | (def! sum2 (fn* (n acc) (if (= n 0) acc (sum2 (- n 1) (+ n acc))))) 4 | 5 | ;; TODO: test let*, and do for TCO 6 | 7 | (sum2 10 0) 8 | ;=>55 9 | 10 | (def! res2 nil) 11 | ;=>nil 12 | (def! res2 (sum2 10000 0)) 13 | res2 14 | ;=>50005000 15 | 16 | 17 | ;; Test mutually recursive tail-call functions 18 | 19 | (def! foo (fn* (n) (if (= n 0) 0 (bar (- n 1))))) 20 | (def! bar (fn* (n) (if (= n 0) 0 (foo (- n 1))))) 21 | 22 | (foo 10000) 23 | ;=>0 24 | -------------------------------------------------------------------------------- /rust/Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | target/release/step0_repl: build 4 | target/release/step1_read_print: build 5 | target/release/step2_eval: build 6 | target/release/step3_env: build 7 | target/release/step4_if_fn_do: build 8 | target/release/step5_tco: build 9 | target/release/step6_file: build 10 | target/release/step7_quote: build 11 | target/release/step8_macros: build 12 | target/release/step9_try: build 13 | target/release/stepA_mal: build 14 | 15 | build: 16 | cargo build --release 17 | 18 | clean: 19 | rm -rf target 20 | -------------------------------------------------------------------------------- /tests/step0_repl.mal: -------------------------------------------------------------------------------- 1 | ;; Testing basic string 2 | abcABC123 3 | ;=>abcABC123 4 | 5 | ;; Testing string containing spaces 6 | hello mal world 7 | ;=>hello mal world 8 | 9 | ;; Testing string containing symbols 10 | []{}"'* ;:() 11 | ;=>[]{}"'* ;:() 12 | 13 | 14 | ;; Test long string 15 | hello world abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 (;:() []{}"'* ;:() []{}"'* ;:() []{}"'*) 16 | ;=>hello world abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 (;:() []{}"'* ;:() []{}"'* ;:() []{}"'*) 17 | 18 | -------------------------------------------------------------------------------- /tests/perf3.mal: -------------------------------------------------------------------------------- 1 | (load-file "../core.mal") 2 | (load-file "../perf.mal") 3 | 4 | ;;(prn "Start: basic macros/atom test") 5 | 6 | (def! atm (atom (list 0 1 2 3 4 5 6 7 8 9))) 7 | 8 | (println "iters/s:" 9 | (run-fn-for 10 | (fn* [] 11 | (do 12 | (or false nil false nil false nil false nil false nil (first @atm)) 13 | (cond false 1 nil 2 false 3 nil 4 false 5 nil 6 "else" (first @atm)) 14 | (-> (deref atm) rest rest rest rest rest rest first) 15 | (swap! atm (fn* [a] (concat (rest a) (list (first a))))))) 16 | 10)) 17 | 18 | ;;(prn "Done: basic macros/atom test") 19 | -------------------------------------------------------------------------------- /mal/step0_repl.mal: -------------------------------------------------------------------------------- 1 | ;; read 2 | (def! READ (fn* [strng] 3 | strng)) 4 | 5 | ;; eval 6 | (def! EVAL (fn* [ast env] 7 | ast)) 8 | 9 | ;; print 10 | (def! PRINT (fn* [exp] exp)) 11 | 12 | ;; repl 13 | (def! rep (fn* [strng] 14 | (PRINT (EVAL (READ strng) {})))) 15 | 16 | ;; repl loop 17 | (def! repl-loop (fn* [] 18 | (let* [line (readline "mal-user> ")] 19 | (if line 20 | (do 21 | (if (not (= "" line)) 22 | (try* 23 | (println (rep line)) 24 | (catch* exc 25 | (println "Uncaught exception:" exc)))) 26 | (repl-loop)))))) 27 | 28 | (def! -main (fn* [& args] 29 | (repl-loop))) 30 | (-main) 31 | -------------------------------------------------------------------------------- /rust/src/util.rs: -------------------------------------------------------------------------------- 1 | use types::*; 2 | 3 | pub fn num_result(arg: &MalType) -> Result { 4 | if let Some(num) = arg.number_val() { 5 | Ok(num) 6 | } else { 7 | Err(MalError::WrongArguments( 8 | format!("Expected a number but got: {:?}", arg).to_string(), 9 | )) 10 | } 11 | } 12 | 13 | pub fn vec_result(arg: &MalType) -> Result, MalError> { 14 | if let Some(vec) = arg.list_or_vector_val() { 15 | Ok(vec.clone()) 16 | } else { 17 | Err(MalError::WrongArguments( 18 | format!("Expected a list or vector but got: {:?}", arg).to_string(), 19 | )) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mal/step1_read_print.mal: -------------------------------------------------------------------------------- 1 | ;; read 2 | (def! READ (fn* [strng] 3 | (read-string strng))) 4 | 5 | ;; eval 6 | (def! EVAL (fn* [ast env] 7 | ast)) 8 | 9 | ;; print 10 | (def! PRINT (fn* [exp] (pr-str exp))) 11 | 12 | ;; repl 13 | (def! rep (fn* [strng] 14 | (PRINT (EVAL (READ strng) {})))) 15 | 16 | ;; repl loop 17 | (def! repl-loop (fn* [] 18 | (let* [line (readline "mal-user> ")] 19 | (if line 20 | (do 21 | (if (not (= "" line)) 22 | (try* 23 | (println (rep line)) 24 | (catch* exc 25 | (println "Uncaught exception:" exc)))) 26 | (repl-loop)))))) 27 | 28 | (def! -main (fn* [& args] 29 | (repl-loop))) 30 | (-main) 31 | -------------------------------------------------------------------------------- /mal/Makefile: -------------------------------------------------------------------------------- 1 | 2 | TESTS = 3 | 4 | SOURCES_BASE = 5 | SOURCES_LISP = env.mal core.mal stepA_mal.mal 6 | SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) 7 | 8 | all: mal.mal 9 | 10 | mal.mal: stepA_mal.mal 11 | cp $< $@ 12 | 13 | clean: 14 | rm -f mal.mal 15 | 16 | #.PHONY: stats tests $(TESTS) 17 | .PHONY: stats 18 | 19 | stats: $(SOURCES) 20 | @wc $^ 21 | @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" 22 | stats-lisp: $(SOURCES_LISP) 23 | @wc $^ 24 | @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" 25 | 26 | #tests: $(TESTS) 27 | # 28 | #$(TESTS): 29 | # @echo "Running $@"; \ 30 | # python $@ || exit 1; \ 31 | -------------------------------------------------------------------------------- /tests/step2_eval.mal: -------------------------------------------------------------------------------- 1 | ;; Testing evaluation of arithmetic operations 2 | (+ 1 2) 3 | ;=>3 4 | 5 | (+ 5 (* 2 3)) 6 | ;=>11 7 | 8 | (- (+ 5 (* 2 3)) 3) 9 | ;=>8 10 | 11 | (/ (- (+ 5 (* 2 3)) 3) 4) 12 | ;=>2 13 | 14 | (/ (- (+ 515 (* 87 311)) 302) 27) 15 | ;=>1010 16 | 17 | (* -3 6) 18 | ;=>-18 19 | 20 | (/ (- (+ 515 (* -87 311)) 296) 27) 21 | ;=>-994 22 | 23 | (abc 1 2 3) 24 | ; .*\'abc\' not found.* 25 | 26 | ;; Testing empty list 27 | () 28 | ;=>() 29 | 30 | ;>>> deferrable=True 31 | ;>>> optional=True 32 | ;; 33 | ;; -------- Deferrable/Optional Functionality -------- 34 | 35 | ;; Testing evaluation within collection literals 36 | [1 2 (+ 1 2)] 37 | ;=>[1 2 3] 38 | 39 | {"a" (+ 7 8)} 40 | ;=>{"a" 15} 41 | 42 | {:a (+ 7 8)} 43 | ;=>{:a 15} 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mal-rust 2 | 3 | This is my own implementation of [mal](https://github.com/kanaka/mal) in Rust. 4 | 5 | I wrote a little about my experience building this 6 | [here](http://seven1m.sdf.org/experiments/make_a_lisp_in_rust.html). 7 | 8 | The main mal repo already has a Rust implementation, so I'll keep this here. 9 | 10 | ## Build 11 | 12 | This has been tested with Rust version 1.33.0. 13 | 14 | ```bash 15 | make rust 16 | ``` 17 | 18 | ## Run the REPL 19 | 20 | ```bash 21 | rust/target/release/stepA_mal 22 | ``` 23 | 24 | ## Run a Mal Program 25 | 26 | ```bash 27 | rust/target/release/stepA_mal examples/hello.mal 28 | ``` 29 | 30 | ## License 31 | 32 | Mal is copyright Joel Martin and licensed under the MPL 2.0 (Mozilla Public License 2.0). 33 | See LICENSE for more details. 34 | -------------------------------------------------------------------------------- /rust/src/bin/step0_repl.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::readline::Readline; 4 | 5 | fn main() { 6 | let mut readline = Readline::new("user> "); 7 | loop { 8 | match readline.get() { 9 | Some(line) => { 10 | if line.len() > 0 { 11 | println!("{}", rep(line)) 12 | } 13 | } 14 | None => break, 15 | } 16 | } 17 | readline.save_history(); 18 | } 19 | 20 | fn rep(input: String) -> String { 21 | let out = read(input); 22 | let out = eval(out); 23 | let out = print(out); 24 | out 25 | } 26 | 27 | fn read(arg: String) -> String { 28 | arg 29 | } 30 | 31 | fn eval(arg: String) -> String { 32 | arg 33 | } 34 | 35 | fn print(arg: String) -> String { 36 | arg 37 | } 38 | -------------------------------------------------------------------------------- /.travis_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | ACTION=${1} 6 | IMPL=${2} 7 | MAL_IMPL=${3:-js} 8 | 9 | mode_var=${IMPL}_MODE 10 | mode_val=${!mode_var} 11 | 12 | echo "ACTION: ${ACTION}" 13 | echo "IMPL: ${IMPL}" 14 | echo "MAL_IMPL: ${MAL_IMPL}" 15 | 16 | # If NO_DOCKER is blank then launch use a docker image, otherwise use 17 | # the Travis image/tools directly. 18 | if [ "${NO_DOCKER}" ]; then 19 | MAKE="make" 20 | else 21 | impl=$(echo "${IMPL}" | tr '[:upper:]' '[:lower:]') 22 | img_impl=$(echo "${3:-${IMPL}}" | tr '[:upper:]' '[:lower:]') 23 | 24 | MAKE="docker run -it -u $(id -u) -v `pwd`:/mal kanaka/mal-test-${img_impl} make" 25 | fi 26 | 27 | ${MAKE} TEST_OPTS="--debug-file ../${ACTION}.err" \ 28 | MAL_IMPL=${MAL_IMPL} \ 29 | ${mode_val:+${mode_var}=${mode_val}} \ 30 | ${ACTION}^${IMPL} 31 | 32 | # no failure so remove error log 33 | rm -f ${ACTION}.err || true 34 | -------------------------------------------------------------------------------- /perf.mal: -------------------------------------------------------------------------------- 1 | (defmacro! time 2 | (fn* (exp) 3 | `(let* (start_FIXME (time-ms) 4 | ret_FIXME ~exp) 5 | (do 6 | (prn (str "Elapsed time: " (- (time-ms) start_FIXME) " msecs")) 7 | ret_FIXME)))) 8 | 9 | (def! run-fn-for* 10 | (fn* [fn max-ms acc-ms iters] 11 | (let* [start (time-ms) 12 | _ (fn) 13 | elapsed (- (time-ms) start) 14 | new-iters (+ 1 iters) 15 | new-acc-ms (+ acc-ms elapsed)] 16 | ;(do (prn "here:" new-acc-ms "/" max-ms "iters:" new-iters) ) 17 | (if (>= new-acc-ms max-ms) 18 | (/ (* max-ms iters) new-acc-ms) 19 | (run-fn-for* fn max-ms new-acc-ms new-iters))))) 20 | 21 | (def! run-fn-for 22 | (fn* [fn max-secs] 23 | (do 24 | ;; Warm it up first 25 | (run-fn-for* fn 1000 0 0) 26 | ;; Now do the test 27 | (/ (run-fn-for* fn (* 1000 max-secs) 0 0) max-secs)))) 28 | -------------------------------------------------------------------------------- /run_argv_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Usage: run_argv_test.sh 5 | # 6 | # Example: run_argv_test.sh python step6_file.py 7 | # 8 | 9 | assert_equal() { 10 | if [ "$1" = "$2" ] ; then 11 | echo "OK: '$1'" 12 | else 13 | echo "FAIL: Expected '$1' but got '$2'" 14 | echo 15 | exit 1 16 | fi 17 | } 18 | 19 | if [ -z "$1" ] ; then 20 | echo "Usage: $0 " 21 | exit 1 22 | fi 23 | 24 | root="$(dirname $0)" 25 | 26 | out="$( $@ $root/tests/print_argv.mal aaa bbb ccc | tr -d '\r' )" 27 | assert_equal '("aaa" "bbb" "ccc")' "$out" 28 | 29 | # Note: The 'make' implementation cannot handle arguments with spaces in them, 30 | # so for now we skip this test. 31 | # 32 | # out="$( $@ $root/tests/print_argv.mal aaa 'bbb ccc' ddd )" 33 | # assert_equal '("aaa" "bbb ccc" "ddd")' "$out" 34 | 35 | out="$( $@ $root/tests/print_argv.mal | tr -d '\r' )" 36 | assert_equal '()' "$out" 37 | 38 | echo 'Passed all *ARGV* tests' 39 | echo 40 | -------------------------------------------------------------------------------- /mal/env.mal: -------------------------------------------------------------------------------- 1 | ;; env 2 | 3 | (def! bind-env (fn* [env b e] 4 | (if (empty? b) 5 | env 6 | 7 | (if (= "&" (str (first b))) 8 | (assoc env (str (nth b 1)) e) 9 | 10 | (bind-env (assoc env (str (first b)) (first e)) 11 | (rest b) (rest e)))))) 12 | 13 | (def! new-env (fn* [& args] 14 | (if (<= (count args) 1) 15 | (atom {"--outer--" (first args)}) 16 | (atom (bind-env {"--outer--" (first args)} 17 | (nth args 1) (nth args 2)))))) 18 | 19 | (def! env-find (fn* [env k] 20 | (let* [ks (str k) 21 | data @env] 22 | (if (contains? data ks) 23 | env 24 | (if (get data "--outer--") 25 | (env-find (get data "--outer--") ks) 26 | nil))))) 27 | 28 | (def! env-get (fn* [env k] 29 | (let* [ks (str k) 30 | e (env-find env ks)] 31 | (if e 32 | (get @e ks) 33 | (throw (str "'" ks "' not found")))))) 34 | 35 | (def! env-set (fn* [env k v] 36 | (do 37 | (swap! env assoc (str k) v) 38 | v))) 39 | 40 | ;;(prn "loaded env.mal") 41 | -------------------------------------------------------------------------------- /mal/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:vivid 2 | MAINTAINER Joel Martin 3 | 4 | ########################################################## 5 | # General requirements for testing or common across many 6 | # implementations 7 | ########################################################## 8 | 9 | RUN apt-get -y update 10 | 11 | # Required for running tests 12 | RUN apt-get -y install make python 13 | 14 | # Some typical implementation and test requirements 15 | RUN apt-get -y install curl libreadline-dev libedit-dev 16 | 17 | RUN mkdir -p /mal 18 | WORKDIR /mal 19 | 20 | ########################################################## 21 | # Specific implementation requirements 22 | ########################################################## 23 | 24 | # For building node modules 25 | RUN apt-get -y install g++ 26 | 27 | # Add nodesource apt repo config for 0.12 stable 28 | RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - 29 | 30 | # Install nodejs 31 | RUN apt-get -y install nodejs 32 | 33 | # Link common name 34 | RUN ln -sf nodejs /usr/bin/node 35 | 36 | ENV NPM_CONFIG_CACHE /mal/.npm 37 | 38 | -------------------------------------------------------------------------------- /.travis_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | BUILD_IMPL=${BUILD_IMPL:-${IMPL}} 6 | 7 | mode_var=${IMPL}_MODE 8 | mode_val=${!mode_var} 9 | 10 | # If NO_DOCKER is blank then launch use a docker image, otherwise 11 | # use the Travis image/tools directly. 12 | if [ -z "${NO_DOCKER}" ]; then 13 | impl=$(echo "${IMPL}" | tr '[:upper:]' '[:lower:]') 14 | img_impl=$(echo "${BUILD_IMPL}" | tr '[:upper:]' '[:lower:]') 15 | 16 | docker pull kanaka/mal-test-${impl} 17 | if [ "${impl}" != "${img_impl}" ]; then 18 | docker pull kanaka/mal-test-${img_impl} 19 | fi 20 | if [ "${BUILD_IMPL}" = "rpython" ]; then 21 | # rpython often fails on step9 in compute_vars_longevity 22 | # so build step9, then continue wit the full build 23 | docker run -it -u $(id -u) -v `pwd`:/mal kanaka/mal-test-${img_impl} \ 24 | make -C ${BUILD_IMPL} step9_try || true 25 | fi 26 | docker run -it -u $(id -u) -v `pwd`:/mal kanaka/mal-test-${img_impl} \ 27 | make ${mode_val:+${mode_var}=${mode_val}} \ 28 | -C ${BUILD_IMPL} 29 | else 30 | make ${mode_val:+${mode_var}=${mode_val}} \ 31 | -C ${BUILD_IMPL} 32 | fi 33 | -------------------------------------------------------------------------------- /rust/src/bin/step1_read_print.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::readline::Readline; 4 | use mal_rust::reader::read_str; 5 | use mal_rust::printer::pr_str; 6 | use mal_rust::types::*; 7 | 8 | fn main() { 9 | let mut readline = Readline::new("user> "); 10 | loop { 11 | match readline.get() { 12 | Some(line) => { 13 | if line.len() > 0 { 14 | let result = rep(line); 15 | match result { 16 | Ok(str) => println!("{}", str), 17 | Err(MalError::BlankLine) => {} 18 | Err(err) => println!("{}", err), 19 | } 20 | } 21 | } 22 | None => break, 23 | } 24 | } 25 | readline.save_history(); 26 | } 27 | 28 | fn rep(input: String) -> Result { 29 | let out = read(input)?; 30 | let out = eval(out); 31 | let out = print(out); 32 | Ok(out) 33 | } 34 | 35 | fn read(arg: String) -> MalResult { 36 | read_str(&arg) 37 | } 38 | 39 | fn eval(arg: MalType) -> MalType { 40 | arg 41 | } 42 | 43 | fn print(arg: MalType) -> String { 44 | pr_str(&arg, true) 45 | } 46 | -------------------------------------------------------------------------------- /rust/src/readline.rs: -------------------------------------------------------------------------------- 1 | use linefeed::{DefaultTerminal, Interface, ReadResult}; 2 | 3 | pub struct Readline { 4 | reader: Interface, 5 | } 6 | 7 | const HISTORY_FILE: &str = ".mal-history"; 8 | 9 | impl Readline { 10 | pub fn new(prompt: &str) -> Readline { 11 | let reader = Interface::new("mal").unwrap(); 12 | reader.set_prompt(prompt).expect("could not set prompt"); 13 | reader.load_history(HISTORY_FILE).unwrap_or(()); 14 | Readline { reader: reader } 15 | } 16 | 17 | pub fn get(&mut self) -> Option { 18 | readline(&mut self.reader) 19 | } 20 | 21 | pub fn save_history(&self) { 22 | self.reader.save_history(HISTORY_FILE).unwrap_or(()); 23 | } 24 | } 25 | 26 | fn readline(reader: &mut Interface) -> Option { 27 | match reader.read_line() { 28 | Ok(read_result) => match read_result { 29 | ReadResult::Input(line) => { 30 | if line.len() > 0 { 31 | reader.add_history(line.clone()); 32 | } 33 | Some(line) 34 | } 35 | _ => None, 36 | }, 37 | Err(err) => { 38 | println!("Error: {:?}", err); 39 | None 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/step3_env.mal: -------------------------------------------------------------------------------- 1 | ;; Testing REPL_ENV 2 | (+ 1 2) 3 | ;=>3 4 | (/ (- (+ 5 (* 2 3)) 3) 4) 5 | ;=>2 6 | 7 | 8 | ;; Testing def! 9 | (def! x 3) 10 | ;=>3 11 | x 12 | ;=>3 13 | (def! x 4) 14 | ;=>4 15 | x 16 | ;=>4 17 | (def! y (+ 1 7)) 18 | ;=>8 19 | y 20 | ;=>8 21 | 22 | ;; Verifying symbols are case-sensitive 23 | (def! mynum 111) 24 | ;=>111 25 | (def! MYNUM 222) 26 | ;=>222 27 | mynum 28 | ;=>111 29 | MYNUM 30 | ;=>222 31 | 32 | ;; Check env lookup non-fatal error 33 | (abc 1 2 3) 34 | ; .*\'abc\' not found.* 35 | ;; Check that error aborts def! 36 | (def! w 123) 37 | (def! w (abc)) 38 | w 39 | ;=>123 40 | 41 | ;; Testing let* 42 | (let* (z 9) z) 43 | ;=>9 44 | (let* (x 9) x) 45 | ;=>9 46 | x 47 | ;=>4 48 | (let* (z (+ 2 3)) (+ 1 z)) 49 | ;=>6 50 | (let* (p (+ 2 3) q (+ 2 p)) (+ p q)) 51 | ;=>12 52 | (def! y (let* (z 7) z)) 53 | y 54 | ;=>7 55 | 56 | ;; Testing outer environment 57 | (def! a 4) 58 | ;=>4 59 | (let* (q 9) q) 60 | ;=>9 61 | (let* (q 9) a) 62 | ;=>4 63 | (let* (z 2) (let* (q 9) a)) 64 | ;=>4 65 | (let* (x 4) (def! a 5)) 66 | ;=>5 67 | a 68 | ;=>4 69 | 70 | ;>>> deferrable=True 71 | ;>>> optional=True 72 | ;; 73 | ;; -------- Deferrable/Optional Functionality -------- 74 | 75 | ;; Testing let* with vector bindings 76 | (let* [z 9] z) 77 | ;=>9 78 | (let* [p (+ 2 3) q (+ 2 p)] (+ p q)) 79 | ;=>12 80 | 81 | ;; Testing vector evaluation 82 | (let* (a 5 b 6) [3 4 a [b 7] 8]) 83 | ;=>[3 4 5 [6 7] 8] 84 | -------------------------------------------------------------------------------- /examples/memoize.mal: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; memoize.mal 3 | ;; 4 | ;; Implement `memoize` using an atom (`mem`) which holds the memoized results 5 | ;; (hash-map from the arguments to the result). When the function is called, 6 | ;; the hash-map is checked to see if the result for the given argument was already 7 | ;; calculated and stored. If this is the case, it is returned immediately; 8 | ;; otherwise, it is calculated and stored in `mem`. 9 | ;; 10 | ;; Adapted from http://clojure.org/atoms 11 | ;; 12 | 13 | ;; Memoize any function 14 | (def! memoize 15 | (fn* [f] 16 | (let* [mem (atom {})] 17 | (fn* [& args] 18 | (let* [key (str args)] 19 | (if (contains? @mem key) 20 | (get @mem key) 21 | (let* [ret (apply f args)] 22 | (do 23 | (swap! mem assoc key ret) 24 | ret)))))))) 25 | 26 | ;; Naive (non-memoized) Fibonacci function 27 | (def! fib 28 | (fn* [n] 29 | (if (<= n 1) 30 | n 31 | (+ (fib (- n 1)) (fib (- n 2)))))) 32 | 33 | 34 | ;; ----------------------------------------------- 35 | ;; Benchmarks 36 | 37 | (load-file "../perf.mal") ; for the 'time' macro 38 | (def! N 32) 39 | 40 | ;; Benchmark naive 'fib' 41 | 42 | (println "fib N=" N ": without memoization:") 43 | (time (fib N)) 44 | ;; "Elapsed time: 14402 msecs" 45 | 46 | 47 | ;; Benchmark memoized 'fib' 48 | 49 | (def! fib (memoize fib)) 50 | 51 | (println "fib N=" N ": with memoization:") 52 | (time (fib N)) 53 | ;; "Elapsed time: 1 msecs" 54 | -------------------------------------------------------------------------------- /examples/pprint.mal: -------------------------------------------------------------------------------- 1 | 2 | (def! spaces- (fn* [indent] 3 | (if (> indent 0) 4 | (str " " (spaces- (- indent 1))) 5 | ""))) 6 | 7 | (def! pp-seq- (fn* [obj indent] 8 | (let* [xindent (+ 1 indent)] 9 | (apply str (pp- (first obj) 0) 10 | (map (fn* [x] (str "\n" (spaces- xindent) 11 | (pp- x xindent))) 12 | (rest obj)))))) 13 | 14 | (def! pp-map- (fn* [obj indent] 15 | (let* [ks (keys obj) 16 | kindent (+ 1 indent) 17 | kwidth (count (seq (str (first ks)))) 18 | vindent (+ 1 (+ kwidth kindent))] 19 | (apply str (pp- (first ks) 0) 20 | " " 21 | (pp- (get obj (first ks)) 0) 22 | (map (fn* [k] (str "\n" (spaces- kindent) 23 | (pp- k kindent) 24 | " " 25 | (pp- (get obj k) vindent))) 26 | (rest (keys obj))))))) 27 | 28 | (def! pp- (fn* [obj indent] 29 | (cond 30 | (list? obj) (str "(" (pp-seq- obj indent) ")") 31 | (vector? obj) (str "[" (pp-seq- obj indent) "]") 32 | (map? obj) (str "{" (pp-map- obj indent) "}") 33 | :else (pr-str obj)))) 34 | 35 | (def! pprint (fn* [obj] 36 | (println (pp- obj 0)))) 37 | 38 | 39 | ;;(pprint '(7 8 9 "ten" [11 12 [13 14]] 15 16)) 40 | ;;(pprint '{:abc 123 :def {:ghi 456 :jkl [789 "ten eleven twelve"]}}) 41 | ;;(pprint '(7 8 {:abc 123 :def {:ghi 456 :jkl 789}} 9 10 [11 12 [13 14]] 15 16)) 42 | -------------------------------------------------------------------------------- /mal/core.mal: -------------------------------------------------------------------------------- 1 | (def! _fn? (fn* [x] 2 | (if (fn? x) 3 | (if (get (meta x) "ismacro") 4 | false 5 | true) 6 | false))) 7 | 8 | (def! macro? (fn* [x] 9 | (if (fn? x) 10 | (if (get (meta x) "ismacro") 11 | true 12 | false) 13 | false))) 14 | 15 | (def! core_ns 16 | [["=" =] 17 | ["throw" throw] 18 | ["nil?" nil?] 19 | ["true?" true?] 20 | ["false?" false?] 21 | ["number?" number?] 22 | ["string?" string?] 23 | ["symbol" symbol] 24 | ["symbol?" symbol?] 25 | ["keyword" keyword] 26 | ["keyword?" keyword?] 27 | ["fn?" _fn?] 28 | ["macro?" macro?] 29 | 30 | ["pr-str" pr-str] 31 | ["str" str] 32 | ["prn" prn] 33 | ["println" println] 34 | ["readline" readline] 35 | ["read-string" read-string] 36 | ["slurp" slurp] 37 | ["<" <] 38 | ["<=" <=] 39 | [">" >] 40 | [">=" >=] 41 | ["+" +] 42 | ["-" -] 43 | ["*" *] 44 | ["/" /] 45 | ["time-ms" time-ms] 46 | 47 | ["list" list] 48 | ["list?" list?] 49 | ["vector" vector] 50 | ["vector?" vector?] 51 | ["hash-map" hash-map] 52 | ["map?" map?] 53 | ["assoc" assoc] 54 | ["dissoc" dissoc] 55 | ["get" get] 56 | ["contains?" contains?] 57 | ["keys" keys] 58 | ["vals" vals] 59 | 60 | ["sequential?" sequential?] 61 | ["cons" cons] 62 | ["concat" concat] 63 | ["nth" nth] 64 | ["first" first] 65 | ["rest" rest] 66 | ["empty?" empty?] 67 | ["count" count] 68 | ["apply" apply] 69 | ["map" map] 70 | 71 | ["conj" conj] 72 | ["seq" seq] 73 | 74 | ["with-meta" with-meta] 75 | ["meta" meta] 76 | ["atom" atom] 77 | ["atom?" atom?] 78 | ["deref" deref] 79 | ["reset!" reset!] 80 | ["swap!" swap!]]) 81 | -------------------------------------------------------------------------------- /mal/step2_eval.mal: -------------------------------------------------------------------------------- 1 | ;; read 2 | (def! READ (fn* [strng] 3 | (read-string strng))) 4 | 5 | 6 | ;; eval 7 | (def! eval-ast (fn* [ast env] (do 8 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 9 | (cond 10 | (symbol? ast) (let* [res (get env (str ast))] 11 | (if res res (throw (str ast " not found")))) 12 | 13 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 14 | 15 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 16 | 17 | (map? ast) (apply hash-map 18 | (apply concat 19 | (map (fn* [k] [k (EVAL (get ast k) env)]) 20 | (keys ast)))) 21 | 22 | "else" ast)))) 23 | 24 | 25 | (def! EVAL (fn* [ast env] (do 26 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 27 | (if (not (list? ast)) 28 | (eval-ast ast env) 29 | 30 | ;; apply list 31 | (if (empty? ast) 32 | ast 33 | (let* [el (eval-ast ast env) 34 | f (first el) 35 | args (rest el)] 36 | (apply f args))))))) 37 | 38 | 39 | ;; print 40 | (def! PRINT (fn* [exp] (pr-str exp))) 41 | 42 | ;; repl 43 | (def! repl-env {"+" + 44 | "-" - 45 | "*" * 46 | "/" /}) 47 | (def! rep (fn* [strng] 48 | (PRINT (EVAL (READ strng) repl-env)))) 49 | 50 | ;; repl loop 51 | (def! repl-loop (fn* [] 52 | (let* [line (readline "mal-user> ")] 53 | (if line 54 | (do 55 | (if (not (= "" line)) 56 | (try* 57 | (println (rep line)) 58 | (catch* exc 59 | (println "Uncaught exception:" exc)))) 60 | (repl-loop)))))) 61 | 62 | (def! -main (fn* [& args] 63 | (repl-loop))) 64 | (-main) 65 | -------------------------------------------------------------------------------- /rust/src/env.rs: -------------------------------------------------------------------------------- 1 | use types::*; 2 | use std::collections::HashMap; 3 | use std::rc::Rc; 4 | use std::cell::RefCell; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct EnvType { 8 | pub outer: Option, 9 | pub data: HashMap, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Env(Rc>); 14 | 15 | impl Env { 16 | pub fn new(outer: Option<&Env>) -> Env { 17 | Env(Rc::new(RefCell::new(EnvType { 18 | outer: outer.map(|e| e.clone()), 19 | data: HashMap::new(), 20 | }))) 21 | } 22 | 23 | pub fn with_binds(outer: Option<&Env>, binds: Vec, mut exprs: Vec) -> Env { 24 | let env = Env::new(outer); 25 | let mut is_more = false; 26 | for bind in binds { 27 | if let Some(name) = bind.symbol_val() { 28 | if name == "&" { 29 | is_more = true; 30 | } else if is_more { 31 | env.set(&name, MalType::list(exprs)); 32 | break; 33 | } else if exprs.len() > 0 { 34 | env.set(&name, exprs.remove(0)); 35 | } 36 | } else { 37 | panic!("Expected a MalType::Symbol!"); 38 | } 39 | } 40 | env 41 | } 42 | 43 | pub fn set(&self, key: &str, val: MalType) { 44 | self.0.borrow_mut().data.insert(key.to_string(), val); 45 | } 46 | 47 | pub fn find(&self, key: &str) -> Option { 48 | if self.0.borrow().data.contains_key(key) { 49 | Some(self.clone()) 50 | } else { 51 | let b = self.0.borrow(); 52 | if let Some(ref outer) = b.outer { 53 | outer.find(key).map(|e| e.clone()) 54 | } else { 55 | None 56 | } 57 | } 58 | } 59 | 60 | pub fn get(&self, key: &str) -> Result { 61 | if let Some(env) = self.find(key) { 62 | if let Some(val) = env.0.borrow().data.get(key) { 63 | return Ok(val.clone()); 64 | } 65 | } 66 | Err(MalError::SymbolUndefined(key.to_string())) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mal/step3_env.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | 3 | ;; read 4 | (def! READ (fn* [strng] 5 | (read-string strng))) 6 | 7 | 8 | ;; eval 9 | (def! eval-ast (fn* [ast env] (do 10 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 11 | (cond 12 | (symbol? ast) (env-get env ast) 13 | 14 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 15 | 16 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 17 | 18 | (map? ast) (apply hash-map 19 | (apply concat 20 | (map (fn* [k] [k (EVAL (get ast k) env)]) 21 | (keys ast)))) 22 | 23 | "else" ast)))) 24 | 25 | (def! LET (fn* [env args] 26 | (if (> (count args) 0) 27 | (do 28 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 29 | (LET env (rest (rest args))))))) 30 | 31 | (def! EVAL (fn* [ast env] (do 32 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 33 | (if (not (list? ast)) 34 | (eval-ast ast env) 35 | 36 | ;; apply list 37 | (let* [a0 (first ast)] 38 | (cond 39 | (nil? a0) 40 | ast 41 | 42 | (= 'def! a0) 43 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 44 | 45 | (= 'let* a0) 46 | (let* [let-env (new-env env)] 47 | (do 48 | (LET let-env (nth ast 1)) 49 | (EVAL (nth ast 2) let-env))) 50 | 51 | "else" 52 | (let* [el (eval-ast ast env) 53 | f (first el) 54 | args (rest el)] 55 | (apply f args)))))))) 56 | 57 | 58 | ;; print 59 | (def! PRINT (fn* [exp] (pr-str exp))) 60 | 61 | ;; repl 62 | (def! repl-env (new-env)) 63 | (def! rep (fn* [strng] 64 | (PRINT (EVAL (READ strng) repl-env)))) 65 | 66 | (env-set repl-env "+" +) 67 | (env-set repl-env "-" -) 68 | (env-set repl-env "*" *) 69 | (env-set repl-env "/" /) 70 | 71 | ;; repl loop 72 | (def! repl-loop (fn* [] 73 | (let* [line (readline "mal-user> ")] 74 | (if line 75 | (do 76 | (if (not (= "" line)) 77 | (try* 78 | (println (rep line)) 79 | (catch* exc 80 | (println "Uncaught exception:" exc)))) 81 | (repl-loop)))))) 82 | 83 | (def! -main (fn* [& args] 84 | (repl-loop))) 85 | (-main) 86 | -------------------------------------------------------------------------------- /core.mal: -------------------------------------------------------------------------------- 1 | (def! inc (fn* (a) (+ a 1))) 2 | 3 | (def! dec (fn* (a) (- a 1))) 4 | 5 | (def! zero? (fn* (n) (= 0 n))) 6 | 7 | (def! reduce 8 | (fn* (f init xs) 9 | (if (> (count xs) 0) 10 | (reduce f (f init (first xs)) (rest xs)) 11 | init))) 12 | 13 | (def! identity (fn* (x) x)) 14 | 15 | (def! every? 16 | (fn* (pred xs) 17 | (if (> (count xs) 0) 18 | (if (pred (first xs)) 19 | (every? pred (rest xs)) 20 | false) 21 | true))) 22 | 23 | (def! not (fn* (x) (if x false true))) 24 | 25 | (def! some 26 | (fn* (pred xs) 27 | (if (> (count xs) 0) 28 | (let* (res (pred (first xs))) 29 | (if (pred (first xs)) 30 | res 31 | (some pred (rest xs)))) 32 | nil))) 33 | 34 | (defmacro! and 35 | (fn* (& xs) 36 | (if (empty? xs) 37 | true 38 | (if (= 1 (count xs)) 39 | (first xs) 40 | (let* (condvar (gensym)) 41 | `(let* (~condvar ~(first xs)) 42 | (if ~condvar (and ~@(rest xs)) ~condvar))))))) 43 | 44 | (defmacro! or 45 | (fn* (& xs) 46 | (if (empty? xs) 47 | nil 48 | (if (= 1 (count xs)) 49 | (first xs) 50 | (let* (condvar (gensym)) 51 | `(let* (~condvar ~(first xs)) 52 | (if ~condvar ~condvar (or ~@(rest xs))))))))) 53 | 54 | (defmacro! cond 55 | (fn* (& clauses) 56 | (if (> (count clauses) 0) 57 | (list 'if (first clauses) 58 | (if (> (count clauses) 1) 59 | (nth clauses 1) 60 | (throw "cond requires an even number of forms")) 61 | (cons 'cond (rest (rest clauses))))))) 62 | 63 | (defmacro! -> 64 | (fn* (x & xs) 65 | (if (empty? xs) 66 | x 67 | (let* (form (first xs) 68 | more (rest xs)) 69 | (if (empty? more) 70 | (if (list? form) 71 | `(~(first form) ~x ~@(rest form)) 72 | (list form x)) 73 | `(-> (-> ~x ~form) ~@more)))))) 74 | 75 | (defmacro! ->> 76 | (fn* (x & xs) 77 | (if (empty? xs) 78 | x 79 | (let* (form (first xs) 80 | more (rest xs)) 81 | (if (empty? more) 82 | (if (list? form) 83 | `(~(first form) ~@(rest form) ~x) 84 | (list form x)) 85 | `(->> (->> ~x ~form) ~@more)))))) 86 | 87 | nil 88 | -------------------------------------------------------------------------------- /examples/protocols.mal: -------------------------------------------------------------------------------- 1 | ;; A sketch of Clojure-like protocols, implemented in Mal 2 | ;; By chouser (Chris Houser) 3 | ;; Original: https://gist.github.com/Chouser/6081ea66d144d13e56fc 4 | 5 | (def! builtin-type (fn* [obj] 6 | (cond 7 | (list? obj) :mal/list 8 | (vector? obj) :mal/vector 9 | (map? obj) :mal/map 10 | (symbol? obj) :mal/symbol 11 | (keyword? obj) :mal/keyword 12 | (atom? obj) :mal/atom 13 | (nil? obj) nil 14 | (true? obj) :mal/bool 15 | (false? obj) :mal/bool))) 16 | 17 | (def! find-protocol-methods (fn* [protocol obj] 18 | (let* [p @protocol] 19 | (or (get p (get (meta obj) :type)) 20 | (get p (builtin-type obj)) 21 | (get p :mal/default))))) 22 | 23 | (def! satisfies? (fn* [protocol obj] 24 | (if (find-protocol-methods protocol obj) true false))) 25 | 26 | (defmacro! defprotocol (fn* [proto-name & methods] 27 | `(do 28 | (def! ~proto-name (atom {})) 29 | ~@(map (fn* [m] 30 | (let* [name (first m), sig (first (rest m))] 31 | `(def! ~name (fn* [this-FIXME & args-FIXME] 32 | (apply (get (find-protocol-methods ~proto-name this-FIXME) 33 | ~(keyword (str name))) 34 | this-FIXME args-FIXME))))) 35 | methods)))) 36 | 37 | (def! extend (fn* [type proto methods & more] 38 | (do 39 | (swap! proto assoc type methods) 40 | (if (first more) 41 | (apply extend type more))))) 42 | 43 | ;;---- 44 | ;; Example: 45 | 46 | (def! make-triangle (fn* [o a] 47 | ^{:type :shape/triangle} {:opposite o, :adjacent a})) 48 | 49 | (def! make-rectangle (fn* [x y] 50 | ^{:type :shape/rectangle} {:width x, :height y})) 51 | 52 | (defprotocol IDraw 53 | (area [this]) 54 | (draw [this])) 55 | 56 | (prn :false-> (satisfies? IDraw (make-triangle 5 5))) ;=> false 57 | 58 | (extend :shape/rectangle 59 | IDraw 60 | {:area (fn* [obj] (* (get obj :width) (get obj :height))) 61 | :draw (fn* [obj] (println "[]"))}) 62 | 63 | (extend :shape/triangle 64 | IDraw 65 | {:area (fn* [obj] (/ (* (get obj :opposite) (get obj :adjacent)) 2)) 66 | :draw (fn* [obj] (println " .\n.."))}) 67 | 68 | (prn :true-> (satisfies? IDraw (make-triangle 5 5))) ;=> true 69 | 70 | (prn :area-> (area (make-triangle 5 4))) ;=> 10 71 | -------------------------------------------------------------------------------- /tests/step6_file.mal: -------------------------------------------------------------------------------- 1 | ;;; TODO: really a step5 test 2 | ;; 3 | ;; Testing that (do (do)) not broken by TCO 4 | (do (do 1 2)) 5 | ;=>2 6 | 7 | ;; 8 | ;; Testing read-string, eval and slurp 9 | (read-string "(1 2 (3 4) nil)") 10 | ;=>(1 2 (3 4) nil) 11 | 12 | (read-string "(+ 2 3)") 13 | ;=>(+ 2 3) 14 | 15 | (read-string "7 ;; comment") 16 | ;=>7 17 | 18 | ;;; Differing output, but make sure no fatal error 19 | (read-string ";; comment") 20 | 21 | 22 | (eval (read-string "(+ 2 3)")) 23 | ;=>5 24 | 25 | (slurp "../tests/test.txt") 26 | ;=>"A line of text\n" 27 | 28 | ;; Testing load-file 29 | 30 | (load-file "../tests/inc.mal") 31 | (inc1 7) 32 | ;=>8 33 | (inc2 7) 34 | ;=>9 35 | (inc3 9) 36 | ;=>12 37 | 38 | ;; 39 | ;; Testing that *ARGV* exists and is an empty list 40 | (list? *ARGV*) 41 | ;=>true 42 | *ARGV* 43 | ;=>() 44 | 45 | ;; 46 | ;; Testing atoms 47 | 48 | (def! inc3 (fn* (a) (+ 3 a))) 49 | 50 | (def! a (atom 2)) 51 | ;=>(atom 2) 52 | 53 | (atom? a) 54 | ;=>true 55 | 56 | (atom? 1) 57 | ;=>false 58 | 59 | (deref a) 60 | ;=>2 61 | 62 | (reset! a 3) 63 | ;=>3 64 | 65 | (deref a) 66 | ;=>3 67 | 68 | (swap! a inc3) 69 | ;=>6 70 | 71 | (deref a) 72 | ;=>6 73 | 74 | (swap! a (fn* (a) a)) 75 | ;=>6 76 | 77 | (swap! a (fn* (a) (* 2 a))) 78 | ;=>12 79 | 80 | (swap! a (fn* (a b) (* a b)) 10) 81 | ;=>120 82 | 83 | (swap! a + 3) 84 | ;=>123 85 | 86 | ;; Testing swap!/closure interaction 87 | (def! inc-it (fn* (a) (+ 1 a))) 88 | (def! atm (atom 7)) 89 | (def! f (fn* () (swap! atm inc-it))) 90 | (f) 91 | ;=>8 92 | (f) 93 | ;=>9 94 | 95 | ;>>> deferrable=True 96 | ;>>> optional=True 97 | ;; 98 | ;; -------- Deferrable/Optional Functionality -------- 99 | 100 | ;; Testing comments in a file 101 | (load-file "../tests/incB.mal") 102 | ; "incB.mal finished" 103 | ;=>"incB.mal return string" 104 | (inc4 7) 105 | ;=>11 106 | (inc5 7) 107 | ;=>12 108 | 109 | ;; Testing map literal across multiple lines in a file 110 | (load-file "../tests/incC.mal") 111 | mymap 112 | ;=>{"a" 1} 113 | 114 | ;; Testing `@` reader macro (short for `deref`) 115 | (def! atm (atom 9)) 116 | @atm 117 | ;=>9 118 | 119 | ;;; TODO: really a step5 test 120 | ;; Testing that vector params not broken by TCO 121 | (def! g (fn* [] 78)) 122 | (g) 123 | ;=>78 124 | (def! g (fn* [a] (+ a 78))) 125 | (g 3) 126 | ;=>81 127 | 128 | -------------------------------------------------------------------------------- /examples/equality.mal: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; equality.mal 3 | ;; 4 | ;; This file checks whether the `=` function correctly implements equality of 5 | ;; hash-maps and sequences (lists and vectors). If not, it redefines the `=` 6 | ;; function with a pure mal (recursive) implementation that only relies on the 7 | ;; native original `=` function for comparing scalars (integers, booleans, 8 | ;; symbols, strings). 9 | ;; 10 | 11 | ;; Save the original (native) `=` as scalar-equal? 12 | (def! scalar-equal? =) 13 | 14 | ;; A simple `and` macro for two argument which doesn't use `=` internally 15 | (defmacro! and2 16 | (fn* [a b] 17 | `(let* (and2_FIXME ~a) 18 | (if and2_FIXME ~b and2_FIXME)))) 19 | 20 | ;; Implement `=` for two sequential arguments 21 | (def! sequential-equal? 22 | (fn* [a b] 23 | (if (scalar-equal? (count a) (count b)) 24 | (if (empty? a) 25 | true 26 | (if (mal-equal? (first a) (first b)) 27 | (sequential-equal? (rest a) (rest b)) 28 | false)) 29 | false))) 30 | 31 | ;; Helper function 32 | (def! hash-map-vals-equal? 33 | (fn* [a b map-keys] 34 | (if (scalar-equal? 0 (count map-keys)) 35 | true 36 | (let* [key (first map-keys)] 37 | (if (and2 38 | (and2 (contains? a key) (contains? b key)) 39 | (mal-equal? (get a key) (get b key))) 40 | (hash-map-vals-equal? a b (rest map-keys)) 41 | false))))) 42 | 43 | ;; Implement `=` for two hash-maps 44 | (def! hash-map-equal? 45 | (fn* [a b] 46 | (let* [keys-a (keys a)] 47 | (if (scalar-equal? (count keys-a) (count (keys b))) 48 | (hash-map-vals-equal? a b keys-a) 49 | false)))) 50 | 51 | ;; This implements = in pure mal (using only scalar-equal? as native impl) 52 | (def! mal-equal? 53 | (fn* [a b] 54 | (cond 55 | (and2 (sequential? a) (sequential? b)) (sequential-equal? a b) 56 | (and2 (map? a) (map? b)) (hash-map-equal? a b) 57 | true (scalar-equal? a b)))) 58 | 59 | (def! hash-map-equality-correct? 60 | (fn* [] 61 | (try* 62 | (and2 (= {:a 1} {:a 1}) 63 | (not (= {:a 1} {:a 1 :b 2}))) 64 | (catch* _ false)))) 65 | 66 | (def! sequence-equality-correct? 67 | (fn* [] 68 | (try* 69 | (and2 (= [:a :b] (list :a :b)) 70 | (not (= [:a :b] [:a :b :c]))) 71 | (catch* _ false)))) 72 | 73 | ;; If the native `=` implementation doesn't support sequences or hash-maps 74 | ;; correctly, replace it with the pure mal implementation 75 | (if (not (and2 (hash-map-equality-correct?) (sequence-equality-correct?))) 76 | (do 77 | (def! = mal-equal?) 78 | (println "equality.mal: Replaced = with pure mal implementation"))) 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bash_history 2 | .cache 3 | .cargo 4 | .config 5 | .mal-history 6 | .crystal 7 | .lein 8 | .m2 9 | .ivy2 10 | .sbt 11 | .npm 12 | .node-gyp 13 | package-lock.json 14 | .elm 15 | */experiments 16 | */node_modules 17 | *.o 18 | *.pyc 19 | */step0_repl 20 | */step1_read_print 21 | */step2_eval 22 | */step3_env 23 | */step4_if_fn_do 24 | */step5_tco 25 | */step6_file 26 | */step7_quote 27 | */step8_macros 28 | */step9_try 29 | */stepA_mal 30 | */mal 31 | */notes 32 | 33 | logs 34 | old 35 | 36 | ada/obj/ 37 | awk/mal.awk 38 | bash/mal.sh 39 | clojure/mal.jar 40 | clojure/target 41 | clojure/.lein-repl-history 42 | coffee/mal.coffee 43 | cs/*.exe 44 | cs/*.dll 45 | cs/*.mdb 46 | d/*.o 47 | elixir/_build 48 | elixir/deps 49 | elixir/erl_crash.dump 50 | elixir/*.ez 51 | erlang/ebin 52 | erlang/.rebar 53 | erlang/src/*.beam 54 | es6/mal.js 55 | es6/.esm-cache 56 | factor/mal.factor 57 | fantom/lib 58 | forth/mal.fs 59 | fsharp/*.exe 60 | fsharp/*.dll 61 | fsharp/*.mdb 62 | go/step* 63 | groovy/*.class 64 | groovy/mal.jar 65 | haskell/*.hi 66 | haskell/*.o 67 | haxe/*.n 68 | haxe/*.py 69 | haxe/cpp/ 70 | haxe/*.js 71 | java/mal.jar 72 | java/target/ 73 | java/dependency-reduced-pom.xml 74 | js/mal.js 75 | js/web/mal.js 76 | kotlin/*.jar 77 | kotlin/.idea 78 | kotlin/*.iml 79 | lua/lib 80 | lua/linenoise.so 81 | lua/mal.lua 82 | make/mal.mk 83 | mal/mal.mal 84 | matlab/octave-workspace 85 | miniMAL/mal.json 86 | nim/nimcache* 87 | objc/*.d 88 | ocaml/*.cmi 89 | ocaml/*.cmo 90 | ocaml/*.swp 91 | ocaml/*.cmx 92 | ocaml/*.o 93 | ocaml/mal_lib.* 94 | objpascal/*.o 95 | objpascal/*.ppu 96 | objpascal/pas-readline 97 | objpascal/regexpr/Source/RegExpr.ppu 98 | perl/mal.pl 99 | perl6/.precomp/ 100 | php/mal.php 101 | ps/mal.ps 102 | python/mal.pyz 103 | r/mal.r 104 | ruby/mal.rb 105 | rust/target/ 106 | rust/Cargo.lock 107 | rust/.cargo 108 | r/lib 109 | scala/mal.jar 110 | scala/target 111 | scala/project 112 | skew/*.js 113 | tcl/mal.tcl 114 | vb/*.exe 115 | vb/*.dll 116 | vimscript/mal.vim 117 | clisp/*.fas 118 | clisp/*.lib 119 | basic/step0_repl.bas 120 | basic/step1_read_print.bas 121 | basic/step2_eval.bas 122 | basic/step3_env.bas 123 | basic/step4_if_fn_do.bas 124 | basic/step5_tco.bas 125 | basic/step6_file.bas 126 | basic/step7_quote.bas 127 | basic/step8_macros.bas 128 | basic/step9_try.bas 129 | basic/stepA_mal.bas 130 | basic/*.prg 131 | common-lisp/*.fasl 132 | common-lisp/*.lib 133 | common-lisp/images/* 134 | common-lisp/hist/* 135 | livescript/*.js 136 | !livescript/node_readline.js 137 | livescript/node_modules 138 | elm/node_modules 139 | elm/elm-stuff 140 | elm/*.js 141 | !elm/node_readline.js 142 | !elm/bootstrap.js 143 | -------------------------------------------------------------------------------- /tests/step1_read_print.mal: -------------------------------------------------------------------------------- 1 | ;; Testing read of numbers 2 | 1 3 | ;=>1 4 | 7 5 | ;=>7 6 | 7 7 | ;=>7 8 | -123 9 | ;=>-123 10 | 11 | 12 | ;; Testing read of symbols 13 | + 14 | ;=>+ 15 | abc 16 | ;=>abc 17 | abc 18 | ;=>abc 19 | abc5 20 | ;=>abc5 21 | abc-def 22 | ;=>abc-def 23 | 24 | 25 | ;; Testing read of lists 26 | (+ 1 2) 27 | ;=>(+ 1 2) 28 | () 29 | ;=>() 30 | (nil) 31 | ;=>(nil) 32 | ((3 4)) 33 | ;=>((3 4)) 34 | (+ 1 (+ 2 3)) 35 | ;=>(+ 1 (+ 2 3)) 36 | ( + 1 (+ 2 3 ) ) 37 | ;=>(+ 1 (+ 2 3)) 38 | (* 1 2) 39 | ;=>(* 1 2) 40 | (** 1 2) 41 | ;=>(** 1 2) 42 | (* -3 6) 43 | ;=>(* -3 6) 44 | 45 | ;; Test commas as whitespace 46 | (1 2, 3,,,,),, 47 | ;=>(1 2 3) 48 | 49 | 50 | ;>>> deferrable=True 51 | 52 | ;; 53 | ;; -------- Deferrable Functionality -------- 54 | 55 | ;; Testing read of nil/true/false 56 | nil 57 | ;=>nil 58 | true 59 | ;=>true 60 | false 61 | ;=>false 62 | 63 | ;; Testing read of strings 64 | "abc" 65 | ;=>"abc" 66 | "abc" 67 | ;=>"abc" 68 | "abc (with parens)" 69 | ;=>"abc (with parens)" 70 | "abc\"def" 71 | ;=>"abc\"def" 72 | ;;;"abc\ndef" 73 | ;;;;=>"abc\ndef" 74 | "" 75 | ;=>"" 76 | 77 | ;; Testing reader errors 78 | ;;; TODO: fix these so they fail correctly 79 | (1 2 80 | ; expected ')', got EOF 81 | [1 2 82 | ; expected ']', got EOF 83 | "abc 84 | ; expected '"', got EOF 85 | (1 "abc 86 | ; expected ')', got EOF 87 | 88 | ;; Testing read of quoting 89 | '1 90 | ;=>(quote 1) 91 | '(1 2 3) 92 | ;=>(quote (1 2 3)) 93 | `1 94 | ;=>(quasiquote 1) 95 | `(1 2 3) 96 | ;=>(quasiquote (1 2 3)) 97 | ~1 98 | ;=>(unquote 1) 99 | ~(1 2 3) 100 | ;=>(unquote (1 2 3)) 101 | `(1 ~a 3) 102 | ;=>(quasiquote (1 (unquote a) 3)) 103 | ~@(1 2 3) 104 | ;=>(splice-unquote (1 2 3)) 105 | 106 | 107 | ;>>> optional=True 108 | ;; 109 | ;; -------- Optional Functionality -------- 110 | 111 | ;; Testing keywords 112 | :kw 113 | ;=>:kw 114 | (:kw1 :kw2 :kw3) 115 | ;=>(:kw1 :kw2 :kw3) 116 | 117 | ;; Testing read of vectors 118 | [+ 1 2] 119 | ;=>[+ 1 2] 120 | [] 121 | ;=>[] 122 | [[3 4]] 123 | ;=>[[3 4]] 124 | [+ 1 [+ 2 3]] 125 | ;=>[+ 1 [+ 2 3]] 126 | [ + 1 [+ 2 3 ] ] 127 | ;=>[+ 1 [+ 2 3]] 128 | 129 | ;; Testing read of hash maps 130 | {"abc" 1} 131 | ;=>{"abc" 1} 132 | {"a" {"b" 2}} 133 | ;=>{"a" {"b" 2}} 134 | {"a" {"b" {"c" 3}}} 135 | ;=>{"a" {"b" {"c" 3}}} 136 | { "a" {"b" { "cde" 3 } }} 137 | ;=>{"a" {"b" {"cde" 3}}} 138 | { :a {:b { :cde 3 } }} 139 | ;=>{:a {:b {:cde 3}}} 140 | 141 | ;; Testing read of comments 142 | ;; whole line comment (not an exception) 143 | 1 ; comment after expression 144 | ;=>1 145 | 1; comment after expression 146 | ;=>1 147 | 148 | ;; Testing read of ^/metadata 149 | ^{"a" 1} [1 2 3] 150 | ;=>(with-meta [1 2 3] {"a" 1}) 151 | 152 | 153 | ;; Testing read of @/deref 154 | @a 155 | ;=>(deref a) 156 | -------------------------------------------------------------------------------- /rust/src/printer.rs: -------------------------------------------------------------------------------- 1 | use types::*; 2 | use std::collections::BTreeMap; 3 | use regex::Regex; 4 | 5 | pub fn pr_str(value: &MalType, print_readably: bool) -> String { 6 | if value.is_nil() { 7 | "nil".to_string() 8 | } else if value.is_true() { 9 | "true".to_string() 10 | } else if value.is_false() { 11 | "false".to_string() 12 | } else if let Some(number) = value.number_val() { 13 | number.to_string() 14 | } else if let Some(symbol) = value.symbol_val() { 15 | symbol.to_string() 16 | } else if let Some(keyword) = value.keyword_val() { 17 | ":".to_string() + keyword 18 | } else if let Some(string) = value.string_val() { 19 | if print_readably { 20 | unescape_single_quotes(&format!("{:?}", string)) 21 | } else { 22 | string.to_owned() 23 | } 24 | } else if let Some(list) = value.list_val() { 25 | pr_list(list, '(', ')', print_readably) 26 | } else if let Some(vector) = value.vector_val() { 27 | pr_list(vector, '[', ']', print_readably) 28 | } else if let Some(map) = value.hashmap_val() { 29 | pr_map(map, print_readably) 30 | } else if value.is_function() { 31 | "#".to_string() 32 | } else if value.is_lambda() { 33 | "#".to_string() 34 | } else if let Some(atom) = value.atom_val() { 35 | format!("(atom {})", pr_str(&(*atom.borrow()), print_readably)).to_string() 36 | } else { 37 | panic!("Unknown type") 38 | } 39 | } 40 | 41 | fn pr_list(list: &Vec, open: char, close: char, print_readably: bool) -> String { 42 | let mut str = String::new(); 43 | str.push(open); 44 | let atoms: Vec = list.iter() 45 | .map(|atom| pr_str(atom, print_readably)) 46 | .collect(); 47 | str.push_str(&atoms.join(" ")); 48 | str.push(close); 49 | str 50 | } 51 | 52 | fn pr_map(map: &BTreeMap, print_readably: bool) -> String { 53 | let mut str = String::new(); 54 | str.push('{'); 55 | let pairs: Vec = map.iter() 56 | .map(|(key, val)| pr_str(key, print_readably) + " " + &pr_str(val, print_readably)) 57 | .collect(); 58 | str.push_str(&pairs.join(" ")); 59 | str.push('}'); 60 | str 61 | } 62 | 63 | const ESCAPED_SINGLE_QUOTE: &str = r#"\\'"#; 64 | 65 | /// Rust likes to escape single quotes -- not sure why. 66 | /// But it breaks our tests. 67 | fn unescape_single_quotes(string: &str) -> String { 68 | let re = Regex::new(ESCAPED_SINGLE_QUOTE).unwrap(); 69 | re.replace_all(string, "'").into_owned() 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | use reader::read_str; 76 | 77 | #[test] 78 | fn test_pr_str() { 79 | let code = "(+ 2 (* 3 4))"; 80 | let ast = read_str(code).unwrap(); 81 | assert_eq!(pr_str(&ast, false), code); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mal/step4_if_fn_do.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | (load-file "../mal/core.mal") 3 | 4 | ;; read 5 | (def! READ (fn* [strng] 6 | (read-string strng))) 7 | 8 | 9 | ;; eval 10 | (def! eval-ast (fn* [ast env] (do 11 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 12 | (cond 13 | (symbol? ast) (env-get env ast) 14 | 15 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 16 | 17 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 18 | 19 | (map? ast) (apply hash-map 20 | (apply concat 21 | (map (fn* [k] [k (EVAL (get ast k) env)]) 22 | (keys ast)))) 23 | 24 | "else" ast)))) 25 | 26 | (def! LET (fn* [env args] 27 | (if (> (count args) 0) 28 | (do 29 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 30 | (LET env (rest (rest args))))))) 31 | 32 | (def! EVAL (fn* [ast env] (do 33 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 34 | (if (not (list? ast)) 35 | (eval-ast ast env) 36 | 37 | ;; apply list 38 | (let* [a0 (first ast)] 39 | (cond 40 | (nil? a0) 41 | ast 42 | 43 | (= 'def! a0) 44 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 45 | 46 | (= 'let* a0) 47 | (let* [let-env (new-env env)] 48 | (do 49 | (LET let-env (nth ast 1)) 50 | (EVAL (nth ast 2) let-env))) 51 | 52 | (= 'do a0) 53 | (let* [el (eval-ast (rest ast) env)] 54 | (nth el (- (count el) 1))) 55 | 56 | (= 'if a0) 57 | (let* [cond (EVAL (nth ast 1) env)] 58 | (if (or (= cond nil) (= cond false)) 59 | (if (> (count ast) 3) 60 | (EVAL (nth ast 3) env) 61 | nil) 62 | (EVAL (nth ast 2) env))) 63 | 64 | (= 'fn* a0) 65 | (fn* [& args] 66 | (EVAL (nth ast 2) (new-env env (nth ast 1) args))) 67 | 68 | "else" 69 | (let* [el (eval-ast ast env) 70 | f (first el) 71 | args (rest el)] 72 | (apply f args)))))))) 73 | 74 | 75 | ;; print 76 | (def! PRINT (fn* [exp] (pr-str exp))) 77 | 78 | ;; repl 79 | (def! repl-env (new-env)) 80 | (def! rep (fn* [strng] 81 | (PRINT (EVAL (READ strng) repl-env)))) 82 | 83 | ;; core.mal: defined directly using mal 84 | (map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns) 85 | 86 | ;; core.mal: defined using the new language itself 87 | (rep "(def! not (fn* [a] (if a false true)))") 88 | 89 | ;; repl loop 90 | (def! repl-loop (fn* [] 91 | (let* [line (readline "mal-user> ")] 92 | (if line 93 | (do 94 | (if (not (= "" line)) 95 | (try* 96 | (println (rep line)) 97 | (catch* exc 98 | (println "Uncaught exception:" exc)))) 99 | (repl-loop)))))) 100 | 101 | (def! -main (fn* [& args] 102 | (repl-loop))) 103 | (-main) 104 | -------------------------------------------------------------------------------- /tests/step8_macros.mal: -------------------------------------------------------------------------------- 1 | ;; Testing trivial macros 2 | (defmacro! one (fn* () 1)) 3 | (one) 4 | ;=>1 5 | (defmacro! two (fn* () 2)) 6 | (two) 7 | ;=>2 8 | 9 | ;; Testing unless macros 10 | (defmacro! unless (fn* (pred a b) `(if ~pred ~b ~a))) 11 | (unless false 7 8) 12 | ;=>7 13 | (unless true 7 8) 14 | ;=>8 15 | (defmacro! unless2 (fn* (pred a b) `(if (not ~pred) ~a ~b))) 16 | (unless2 false 7 8) 17 | ;=>7 18 | (unless2 true 7 8) 19 | ;=>8 20 | 21 | ;; Testing macroexpand 22 | (macroexpand (unless2 2 3 4)) 23 | ;=>(if (not 2) 3 4) 24 | 25 | ;; Testing evaluation of macro result 26 | (defmacro! identity (fn* (x) x)) 27 | (let* (a 123) (identity a)) 28 | ;=>123 29 | 30 | 31 | ;>>> deferrable=True 32 | ;; 33 | ;; -------- Deferrable Functionality -------- 34 | 35 | ;; Testing non-macro function 36 | (not (= 1 1)) 37 | ;=>false 38 | ;;; This should fail if it is a macro 39 | (not (= 1 2)) 40 | ;=>true 41 | 42 | ;; Testing nth, first and rest functions 43 | 44 | (nth (list 1) 0) 45 | ;=>1 46 | (nth (list 1 2) 1) 47 | ;=>2 48 | (def! x "x") 49 | (def! x (nth (list 1 2) 2)) 50 | x 51 | ;=>"x" 52 | 53 | (first (list)) 54 | ;=>nil 55 | (first (list 6)) 56 | ;=>6 57 | (first (list 7 8 9)) 58 | ;=>7 59 | 60 | (rest (list)) 61 | ;=>() 62 | (rest (list 6)) 63 | ;=>() 64 | (rest (list 7 8 9)) 65 | ;=>(8 9) 66 | 67 | 68 | ;; Testing or macro 69 | (or) 70 | ;=>nil 71 | (or 1) 72 | ;=>1 73 | (or 1 2 3 4) 74 | ;=>1 75 | (or false 2) 76 | ;=>2 77 | (or false nil 3) 78 | ;=>3 79 | (or false nil false false nil 4) 80 | ;=>4 81 | (or false nil 3 false nil 4) 82 | ;=>3 83 | (or (or false 4)) 84 | ;=>4 85 | 86 | ;; Testing cond macro 87 | 88 | (cond) 89 | ;=>nil 90 | (cond true 7) 91 | ;=>7 92 | (cond true 7 true 8) 93 | ;=>7 94 | (cond false 7 true 8) 95 | ;=>8 96 | (cond false 7 false 8 "else" 9) 97 | ;=>9 98 | (cond false 7 (= 2 2) 8 "else" 9) 99 | ;=>8 100 | (cond false 7 false 8 false 9) 101 | ;=>nil 102 | 103 | ;; Testing EVAL in let* 104 | 105 | (let* (x (or nil "yes")) x) 106 | ;=>"yes" 107 | 108 | 109 | ;>>> optional=True 110 | ;; 111 | ;; -------- Optional Functionality -------- 112 | 113 | ;; Testing nth, first, rest with vectors 114 | 115 | (nth [1] 0) 116 | ;=>1 117 | (nth [1 2] 1) 118 | ;=>2 119 | (def! x "x") 120 | (def! x (nth [1 2] 2)) 121 | x 122 | ;=>"x" 123 | 124 | (first []) 125 | ;=>nil 126 | (first nil) 127 | ;=>nil 128 | (first [10]) 129 | ;=>10 130 | (first [10 11 12]) 131 | ;=>10 132 | (rest []) 133 | ;=>() 134 | (rest nil) 135 | ;=>() 136 | (rest [10]) 137 | ;=>() 138 | (rest [10 11 12]) 139 | ;=>(11 12) 140 | 141 | ;; Testing EVAL in vector let* 142 | 143 | (let* [x (or nil "yes")] x) 144 | ;=>"yes" 145 | 146 | ;; 147 | ;; Loading core.mal 148 | (load-file "../core.mal") 149 | 150 | ;; Testing -> macro 151 | (-> 7) 152 | ;=>7 153 | (-> (list 7 8 9) first) 154 | ;=>7 155 | (-> (list 7 8 9) (first)) 156 | ;=>7 157 | (-> (list 7 8 9) first (+ 7)) 158 | ;=>14 159 | (-> (list 7 8 9) rest (rest) first (+ 7)) 160 | ;=>16 161 | 162 | ;; Testing ->> macro 163 | (->> "L") 164 | ;=>"L" 165 | (->> "L" (str "A") (str "M")) 166 | ;=>"MAL" 167 | (->> [4] (concat [3]) (concat [2]) rest (concat [1])) 168 | ;=>(1 3 4) 169 | 170 | -------------------------------------------------------------------------------- /mal/step6_file.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | (load-file "../mal/core.mal") 3 | 4 | ;; read 5 | (def! READ (fn* [strng] 6 | (read-string strng))) 7 | 8 | 9 | ;; eval 10 | (def! eval-ast (fn* [ast env] (do 11 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 12 | (cond 13 | (symbol? ast) (env-get env ast) 14 | 15 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 16 | 17 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 18 | 19 | (map? ast) (apply hash-map 20 | (apply concat 21 | (map (fn* [k] [k (EVAL (get ast k) env)]) 22 | (keys ast)))) 23 | 24 | "else" ast)))) 25 | 26 | (def! LET (fn* [env args] 27 | (if (> (count args) 0) 28 | (do 29 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 30 | (LET env (rest (rest args))))))) 31 | 32 | (def! EVAL (fn* [ast env] (do 33 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 34 | (if (not (list? ast)) 35 | (eval-ast ast env) 36 | 37 | ;; apply list 38 | (let* [a0 (first ast)] 39 | (cond 40 | (nil? a0) 41 | ast 42 | 43 | (= 'def! a0) 44 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 45 | 46 | (= 'let* a0) 47 | (let* [let-env (new-env env)] 48 | (do 49 | (LET let-env (nth ast 1)) 50 | (EVAL (nth ast 2) let-env))) 51 | 52 | (= 'do a0) 53 | (let* [el (eval-ast (rest ast) env)] 54 | (nth el (- (count el) 1))) 55 | 56 | (= 'if a0) 57 | (let* [cond (EVAL (nth ast 1) env)] 58 | (if (or (= cond nil) (= cond false)) 59 | (if (> (count ast) 3) 60 | (EVAL (nth ast 3) env) 61 | nil) 62 | (EVAL (nth ast 2) env))) 63 | 64 | (= 'fn* a0) 65 | (fn* [& args] 66 | (EVAL (nth ast 2) (new-env env (nth ast 1) args))) 67 | 68 | "else" 69 | (let* [el (eval-ast ast env) 70 | f (first el) 71 | args (rest el)] 72 | (apply f args)))))))) 73 | 74 | 75 | ;; print 76 | (def! PRINT (fn* [exp] (pr-str exp))) 77 | 78 | ;; repl 79 | (def! repl-env (new-env)) 80 | (def! rep (fn* [strng] 81 | (PRINT (EVAL (READ strng) repl-env)))) 82 | 83 | ;; core.mal: defined directly using mal 84 | (map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns) 85 | (env-set repl-env 'eval (fn* [ast] (EVAL ast repl-env))) 86 | (env-set repl-env '*ARGV* (rest *ARGV*)) 87 | 88 | ;; core.mal: defined using the new language itself 89 | (rep "(def! not (fn* [a] (if a false true)))") 90 | (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") 91 | 92 | ;; repl loop 93 | (def! repl-loop (fn* [] 94 | (let* [line (readline "mal-user> ")] 95 | (if line 96 | (do 97 | (if (not (= "" line)) 98 | (try* 99 | (println (rep line)) 100 | (catch* exc 101 | (println "Uncaught exception:" exc)))) 102 | (repl-loop)))))) 103 | 104 | (def! -main (fn* [& args] 105 | (if (> (count args) 0) 106 | (rep (str "(load-file \"" (first args) "\")")) 107 | (repl-loop)))) 108 | (apply -main *ARGV*) 109 | -------------------------------------------------------------------------------- /examples/presentation.mal: -------------------------------------------------------------------------------- 1 | ;; Mal Presentation 2 | 3 | (def! clear 4 | (fn* () 5 | (str ""))) 6 | 7 | (def! bold 8 | (fn* (s) 9 | (str "" s ""))) 10 | 11 | (def! blue 12 | (fn* (s) 13 | (str "" s ""))) 14 | 15 | (def! title 16 | (fn* (s) 17 | (bold (blue (str s "\n"))))) 18 | 19 | (def! title2 20 | (fn* (s) 21 | (bold (blue s)))) 22 | 23 | 24 | (def! slides 25 | (list 26 | (list 27 | (title2 " __ __ _ _") 28 | (title2 "| \/ | / \ | |") 29 | (title2 "| |\/| | / _ \ | | ") 30 | (title2 "| | | |/ ___ \| |___ ") 31 | (title2 "|_| |_/_/ \_\_____|")) 32 | (list 33 | (title "gherkin") 34 | "- a lisp1 written in bash4") 35 | (list 36 | (title "mal - an interpreter for a subset of Clojure")) 37 | (list 38 | (title "mal - an interpreter for a subset of Clojure") 39 | "- written in GNU make") 40 | (list 41 | (title "mal - an interpreter for a subset of Clojure") 42 | "- written in GNU make" 43 | "- and Bash 4") 44 | (list 45 | (title "mal - an interpreter for a subset of Clojure") 46 | "- written in GNU make" 47 | "- and Bash 4" 48 | "- and Javascript") 49 | (list 50 | (title "mal - an interpreter for a subset of Clojure") 51 | "- written in GNU make" 52 | "- and Bash 4" 53 | "- and Javascript" 54 | "- and Python") 55 | (list 56 | (title "mal - an interpreter for a subset of Clojure") 57 | "- written in GNU make" 58 | "- and Bash 4" 59 | "- and Javascript" 60 | "- and Python" 61 | "- and Clojure") 62 | (list 63 | (title "mal - an interpreter for a subset of Clojure") 64 | "- written in GNU make" 65 | "- and Bash 4" 66 | "- and Javascript" 67 | "- and Python" 68 | "- and Clojure" 69 | "- and 17 other languages") 70 | (list 71 | (title "things it has") 72 | "- scalars: integers, strings, symbols, keywords, nil, true, false" 73 | "- immutable collections: lists, vectors, hash-maps" 74 | "- metadata, atoms" 75 | "- def!, fn*, let*" 76 | " - varargs: (fn* (x y & more) ...)" 77 | "- tail call optimization" 78 | " - except GNU make implementation (no iteration)" 79 | "- macros (quote, unquote, quasiquote, splice-quote)" 80 | "- over 500 unit tests" 81 | "- REPL with line editing (GNU readline/libedit/linenoise)") 82 | (list 83 | (title "things it does not have") 84 | "- performance" 85 | "- namespaces" 86 | "- GC (in bash, make, C implementations)" 87 | "- protocols :-(" 88 | "- lots of other things") 89 | (list 90 | (title "why?") 91 | "- because!") 92 | (list 93 | (title "why?") 94 | "- because!" 95 | "- gherkin was an inspiration to higher levels of crazy" 96 | "- evolved into learning tool" 97 | "- way to learn about Lisp and also the target language" 98 | "- each implementation broken into small 11 steps") 99 | (list 100 | (title "thanks to:") 101 | "- Peter Norvig: inspiration: lispy" 102 | " - http://norvig.com/lispy.html" 103 | "- Alan Dipert: gherkin, original gherkin slides" 104 | " - https://github.com/alandipert/gherkin") 105 | (list 106 | (title "mal - Make a Lisp") 107 | "https://github.com/kanaka/mal") 108 | (list 109 | (title "demo")))) 110 | 111 | (def! present 112 | (fn* (slides) 113 | (if (> (count slides) 0) 114 | (do 115 | (println (clear)) 116 | 117 | (apply println (map (fn* (line) (str "\n " line)) (first slides))) 118 | (println "\n\n\n") 119 | (readline "") 120 | (present (rest slides)))))) 121 | 122 | (present slides) 123 | 124 | -------------------------------------------------------------------------------- /examples/clojurewest2014.mal: -------------------------------------------------------------------------------- 1 | ;; Mal Presentation 2 | 3 | (def! clear 4 | (fn* () 5 | (str ""))) 6 | 7 | (def! bold 8 | (fn* (s) 9 | (str "" s ""))) 10 | 11 | (def! blue 12 | (fn* (s) 13 | (str "" s ""))) 14 | 15 | (def! title 16 | (fn* (s) 17 | (bold (blue (str s "\n"))))) 18 | 19 | (def! title2 20 | (fn* (s) 21 | (bold (blue s)))) 22 | 23 | 24 | (def! conj-slides 25 | (list 26 | (list 27 | (title2 " __ __ _ _") 28 | (title2 "| \\/ | / \\ | |") 29 | (title2 "| |\\/| | / _ \\ | | ") 30 | (title2 "| | | |/ ___ \\| |___ ") 31 | (title2 "|_| |_/_/ \\_\\_____|")) 32 | (list 33 | (title "gherkin") 34 | "- a lisp1 written in bash4") 35 | (list 36 | (title "mal - an interpreter for a subset of Clojure")) 37 | (list 38 | (title "mal - an interpreter for a subset of Clojure") 39 | "- written in GNU make") 40 | (list 41 | (title "mal - an interpreter for a subset of Clojure") 42 | "- written in GNU make" 43 | "- and Bash 4") 44 | (list 45 | (title "mal - an interpreter for a subset of Clojure") 46 | "- written in GNU make" 47 | "- and Bash 4" 48 | "- and Javascript") 49 | (list 50 | (title "mal - an interpreter for a subset of Clojure") 51 | "- written in GNU make" 52 | "- and Bash 4" 53 | "- and Javascript" 54 | "- and Python") 55 | (list 56 | (title "mal - an interpreter for a subset of Clojure") 57 | "- written in GNU make" 58 | "- and Bash 4" 59 | "- and Javascript" 60 | "- and Python" 61 | "- and Clojure") 62 | (list 63 | (title "mal - an interpreter for a subset of Clojure") 64 | "- written in GNU make" 65 | "- and Bash 4" 66 | "- and Javascript" 67 | "- and Python" 68 | "- and Clojure" 69 | "- and C and Java and PHP") 70 | (list 71 | (title "things it has") 72 | "- scalars: integers, strings, symbols, nil, true, false" 73 | "- immutable collections: lists, vectors, hash-maps" 74 | "- metadata, atoms" 75 | "- def!, fn*, let*" 76 | " - varargs: (fn* (x y & more) ...)" 77 | "- tail call optimization" 78 | " - except GNU make implementation (no iteration)" 79 | "- macros (quote, unquote, quasiquote, splice-quote)" 80 | "- almost 300 unit tests" 81 | "- REPL with readline (GNU readline or libedit)") 82 | (list 83 | (title "things it does not have") 84 | "- performance" 85 | "- namespaces" 86 | "- keywords" 87 | "- GC (in bash, make, C implementations)" 88 | "- lots of other things") 89 | (list 90 | (title "why?") 91 | "- because!") 92 | (list 93 | (title "why?") 94 | "- because!" 95 | "- gherkin was an inspiration to higher levels of crazy" 96 | "- evolved into learning tool" 97 | "- way to learn about Lisp and also the target language" 98 | "- each implementation broken into small 10 steps") 99 | (list 100 | (title "thanks to:") 101 | "- Peter Norvig: inspiration: lispy" 102 | " - http://norvig.com/lispy.html" 103 | "- Alan Dipert: gherkin, original gherkin slides" 104 | " - https://github.com/alandipert/gherkin") 105 | (list 106 | (title "mal - Make a Lisp") 107 | "https://github.com/kanaka/mal") 108 | (list 109 | (title "demo")))) 110 | 111 | (def! present 112 | (fn* (slides) 113 | (if (> (count slides) 0) 114 | (do 115 | ;;(py!* "import os; r = os.system('clear')") 116 | ;;(sh* "clear") 117 | ;;(make* "$(shell clear)") 118 | (println (clear)) 119 | 120 | ;;(prn (first slides)) 121 | (apply println (map (fn* (line) (str "\n " line)) (first slides))) 122 | (println "\n\n\n") 123 | (readline "") 124 | (present (rest slides)))))) 125 | 126 | (present conj-slides) 127 | 128 | -------------------------------------------------------------------------------- /tests/step7_quote.mal: -------------------------------------------------------------------------------- 1 | ;; Testing cons function 2 | (cons 1 (list)) 3 | ;=>(1) 4 | (cons 1 (list 2)) 5 | ;=>(1 2) 6 | (cons 1 (list 2 3)) 7 | ;=>(1 2 3) 8 | (cons (list 1) (list 2 3)) 9 | ;=>((1) 2 3) 10 | 11 | (def! a (list 2 3)) 12 | (cons 1 a) 13 | ;=>(1 2 3) 14 | a 15 | ;=>(2 3) 16 | 17 | ;; Testing concat function 18 | (concat) 19 | ;=>() 20 | (concat (list 1 2)) 21 | ;=>(1 2) 22 | (concat (list 1 2) (list 3 4)) 23 | ;=>(1 2 3 4) 24 | (concat (list 1 2) (list 3 4) (list 5 6)) 25 | ;=>(1 2 3 4 5 6) 26 | (concat (concat)) 27 | ;=>() 28 | (concat (list) (list)) 29 | ;=>() 30 | 31 | (def! a (list 1 2)) 32 | (def! b (list 3 4)) 33 | (concat a b (list 5 6)) 34 | ;=>(1 2 3 4 5 6) 35 | a 36 | ;=>(1 2) 37 | b 38 | ;=>(3 4) 39 | 40 | ;; Testing regular quote 41 | (quote 7) 42 | ;=>7 43 | (quote (1 2 3)) 44 | ;=>(1 2 3) 45 | (quote (1 2 (3 4))) 46 | ;=>(1 2 (3 4)) 47 | 48 | ;; Testing simple quasiquote 49 | (quasiquote 7) 50 | ;=>7 51 | (quasiquote (1 2 3)) 52 | ;=>(1 2 3) 53 | (quasiquote (1 2 (3 4))) 54 | ;=>(1 2 (3 4)) 55 | (quasiquote (nil)) 56 | ;=>(nil) 57 | 58 | ;; Testing unquote 59 | (quasiquote (unquote 7)) 60 | ;=>7 61 | (def! a 8) 62 | ;=>8 63 | (quasiquote a) 64 | ;=>a 65 | (quasiquote (unquote a)) 66 | ;=>8 67 | (quasiquote (1 a 3)) 68 | ;=>(1 a 3) 69 | (quasiquote (1 (unquote a) 3)) 70 | ;=>(1 8 3) 71 | (def! b (quote (1 "b" "d"))) 72 | ;=>(1 "b" "d") 73 | (quasiquote (1 b 3)) 74 | ;=>(1 b 3) 75 | (quasiquote (1 (unquote b) 3)) 76 | ;=>(1 (1 "b" "d") 3) 77 | (quasiquote ((unquote 1) (unquote 2))) 78 | ;=>(1 2) 79 | 80 | ;; Testing splice-unquote 81 | (def! c (quote (1 "b" "d"))) 82 | ;=>(1 "b" "d") 83 | (quasiquote (1 c 3)) 84 | ;=>(1 c 3) 85 | (quasiquote (1 (splice-unquote c) 3)) 86 | ;=>(1 1 "b" "d" 3) 87 | 88 | 89 | ;; Testing symbol equality 90 | (= (quote abc) (quote abc)) 91 | ;=>true 92 | (= (quote abc) (quote abcd)) 93 | ;=>false 94 | (= (quote abc) "abc") 95 | ;=>false 96 | (= "abc" (quote abc)) 97 | ;=>false 98 | (= "abc" (str (quote abc))) 99 | ;=>true 100 | (= (quote abc) nil) 101 | ;=>false 102 | (= nil (quote abc)) 103 | ;=>false 104 | 105 | ;;;;; Test quine 106 | ;;; TODO: needs expect line length fix 107 | ;;;((fn* [q] (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* [q] (quasiquote ((unquote q) (quote (unquote q))))))) 108 | ;;;=>((fn* [q] (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* [q] (quasiquote ((unquote q) (quote (unquote q))))))) 109 | 110 | ;>>> deferrable=True 111 | ;; 112 | ;; -------- Deferrable Functionality -------- 113 | 114 | ;; Testing ' (quote) reader macro 115 | '7 116 | ;=>7 117 | '(1 2 3) 118 | ;=>(1 2 3) 119 | '(1 2 (3 4)) 120 | ;=>(1 2 (3 4)) 121 | 122 | ;; Testing ` (quasiquote) reader macro 123 | `7 124 | ;=>7 125 | `(1 2 3) 126 | ;=>(1 2 3) 127 | `(1 2 (3 4)) 128 | ;=>(1 2 (3 4)) 129 | `(nil) 130 | ;=>(nil) 131 | 132 | ;; Testing ~ (unquote) reader macro 133 | `~7 134 | ;=>7 135 | (def! a 8) 136 | ;=>8 137 | `(1 ~a 3) 138 | ;=>(1 8 3) 139 | (def! b '(1 "b" "d")) 140 | ;=>(1 "b" "d") 141 | `(1 b 3) 142 | ;=>(1 b 3) 143 | `(1 ~b 3) 144 | ;=>(1 (1 "b" "d") 3) 145 | 146 | ;; Testing ~@ (splice-unquote) reader macro 147 | (def! c '(1 "b" "d")) 148 | ;=>(1 "b" "d") 149 | `(1 c 3) 150 | ;=>(1 c 3) 151 | `(1 ~@c 3) 152 | ;=>(1 1 "b" "d" 3) 153 | 154 | 155 | ;>>> optional=True 156 | ;; 157 | ;; -------- Optional Functionality -------- 158 | 159 | ;; Testing cons, concat, first, rest with vectors 160 | 161 | (cons [1] [2 3]) 162 | ;=>([1] 2 3) 163 | (cons 1 [2 3]) 164 | ;=>(1 2 3) 165 | (concat [1 2] (list 3 4) [5 6]) 166 | ;=>(1 2 3 4 5 6) 167 | 168 | ;; Testing unquote with vectors 169 | (def! a 8) 170 | ;=>8 171 | `[1 a 3] 172 | ;=>(1 a 3) 173 | ;;; TODO: fix this 174 | ;;;;=>[1 a 3] 175 | 176 | ;; Testing splice-unquote with vectors 177 | (def! c '(1 "b" "d")) 178 | ;=>(1 "b" "d") 179 | `[1 ~@c 3] 180 | ;=>(1 1 "b" "d" 3) 181 | ;;; TODO: fix this 182 | ;;;;=>[1 1 "b" "d" 3] 183 | 184 | -------------------------------------------------------------------------------- /mal/step7_quote.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | (load-file "../mal/core.mal") 3 | 4 | ;; read 5 | (def! READ (fn* [strng] 6 | (read-string strng))) 7 | 8 | 9 | ;; eval 10 | (def! is-pair (fn* [x] 11 | (if (sequential? x) 12 | (if (> (count x) 0) 13 | true)))) 14 | 15 | (def! QUASIQUOTE (fn* [ast] 16 | (cond 17 | (not (is-pair ast)) 18 | (list 'quote ast) 19 | 20 | (= 'unquote (first ast)) 21 | (nth ast 1) 22 | 23 | (if (is-pair (first ast)) 24 | (if (= 'splice-unquote (first (first ast))) 25 | true)) 26 | (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) 27 | 28 | "else" 29 | (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) 30 | 31 | (def! eval-ast (fn* [ast env] (do 32 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 33 | (cond 34 | (symbol? ast) (env-get env ast) 35 | 36 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 37 | 38 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 39 | 40 | (map? ast) (apply hash-map 41 | (apply concat 42 | (map (fn* [k] [k (EVAL (get ast k) env)]) 43 | (keys ast)))) 44 | 45 | "else" ast)))) 46 | 47 | (def! LET (fn* [env args] 48 | (if (> (count args) 0) 49 | (do 50 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 51 | (LET env (rest (rest args))))))) 52 | 53 | (def! EVAL (fn* [ast env] (do 54 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 55 | (if (not (list? ast)) 56 | (eval-ast ast env) 57 | 58 | ;; apply list 59 | (let* [a0 (first ast)] 60 | (cond 61 | (nil? a0) 62 | ast 63 | 64 | (= 'def! a0) 65 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 66 | 67 | (= 'let* a0) 68 | (let* [let-env (new-env env)] 69 | (do 70 | (LET let-env (nth ast 1)) 71 | (EVAL (nth ast 2) let-env))) 72 | 73 | (= 'quote a0) 74 | (nth ast 1) 75 | 76 | (= 'quasiquote a0) 77 | (let* [a1 (nth ast 1)] 78 | (EVAL (QUASIQUOTE a1) env)) 79 | 80 | (= 'do a0) 81 | (let* [el (eval-ast (rest ast) env)] 82 | (nth el (- (count el) 1))) 83 | 84 | (= 'if a0) 85 | (let* [cond (EVAL (nth ast 1) env)] 86 | (if (or (= cond nil) (= cond false)) 87 | (if (> (count ast) 3) 88 | (EVAL (nth ast 3) env) 89 | nil) 90 | (EVAL (nth ast 2) env))) 91 | 92 | (= 'fn* a0) 93 | (fn* [& args] 94 | (EVAL (nth ast 2) (new-env env (nth ast 1) args))) 95 | 96 | "else" 97 | (let* [el (eval-ast ast env) 98 | f (first el) 99 | args (rest el)] 100 | (apply f args)))))))) 101 | 102 | 103 | ;; print 104 | (def! PRINT (fn* [exp] (pr-str exp))) 105 | 106 | ;; repl 107 | (def! repl-env (new-env)) 108 | (def! rep (fn* [strng] 109 | (PRINT (EVAL (READ strng) repl-env)))) 110 | 111 | ;; core.mal: defined directly using mal 112 | (map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns) 113 | (env-set repl-env 'eval (fn* [ast] (EVAL ast repl-env))) 114 | (env-set repl-env '*ARGV* (rest *ARGV*)) 115 | 116 | ;; core.mal: defined using the new language itself 117 | (rep "(def! not (fn* [a] (if a false true)))") 118 | (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") 119 | 120 | ;; repl loop 121 | (def! repl-loop (fn* [] 122 | (let* [line (readline "mal-user> ")] 123 | (if line 124 | (do 125 | (if (not (= "" line)) 126 | (try* 127 | (println (rep line)) 128 | (catch* exc 129 | (println "Uncaught exception:" exc)))) 130 | (repl-loop)))))) 131 | 132 | (def! -main (fn* [& args] 133 | (if (> (count args) 0) 134 | (rep (str "(load-file \"" (first args) "\")")) 135 | (repl-loop)))) 136 | (apply -main *ARGV*) 137 | -------------------------------------------------------------------------------- /rust/src/bin/step2_eval.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::core; 4 | use mal_rust::printer::pr_str; 5 | use mal_rust::reader::read_str; 6 | use mal_rust::readline::Readline; 7 | use mal_rust::types::*; 8 | 9 | use std::collections::HashMap; 10 | use std::collections::BTreeMap; 11 | 12 | fn main() { 13 | let mut readline = Readline::new("user> "); 14 | loop { 15 | match readline.get() { 16 | Some(line) => { 17 | if line.len() > 0 { 18 | let result = rep(line); 19 | match result { 20 | Ok(str) => println!("{}", str), 21 | Err(MalError::BlankLine) => {} 22 | Err(err) => println!("{}", err), 23 | } 24 | } 25 | } 26 | None => break, 27 | } 28 | } 29 | readline.save_history(); 30 | } 31 | 32 | type ReplEnv = HashMap; 33 | 34 | fn rep(input: String) -> Result { 35 | let mut repl_env: ReplEnv = HashMap::new(); 36 | repl_env.insert( 37 | "+".to_string(), 38 | MalType::function(Function { 39 | func: Box::new(core::add), 40 | env: None, 41 | }), 42 | ); 43 | repl_env.insert( 44 | "-".to_string(), 45 | MalType::function(Function { 46 | func: Box::new(core::subtract), 47 | env: None, 48 | }), 49 | ); 50 | repl_env.insert( 51 | "*".to_string(), 52 | MalType::function(Function { 53 | func: Box::new(core::multiply), 54 | env: None, 55 | }), 56 | ); 57 | repl_env.insert( 58 | "/".to_string(), 59 | MalType::function(Function { 60 | func: Box::new(core::divide), 61 | env: None, 62 | }), 63 | ); 64 | let out = read(input)?; 65 | let out = eval(out, &repl_env)?; 66 | let out = print(out); 67 | Ok(out) 68 | } 69 | 70 | fn read(code: String) -> MalResult { 71 | read_str(&code) 72 | } 73 | 74 | fn eval(ast: MalType, repl_env: &ReplEnv) -> MalResult { 75 | if ast.is_list() { 76 | if list_len(&ast) == 0 { 77 | Ok(ast) 78 | } else { 79 | let new_ast = eval_ast(ast, repl_env)?; 80 | if let Some(vec) = new_ast.list_val() { 81 | if vec.len() > 0 { 82 | let mut vec = vec.clone(); 83 | let first = vec.remove(0); 84 | if let Some(Function { func, .. }) = first.function_val() { 85 | func(&mut vec, None) 86 | } else { 87 | Err(MalError::NotAFunction(first.clone())) 88 | } 89 | } else { 90 | panic!("Eval'd list is empty!") 91 | } 92 | } else { 93 | panic!("Eval'd list is no longer a list!") 94 | } 95 | } 96 | } else { 97 | Ok(eval_ast(ast, repl_env)?) 98 | } 99 | } 100 | 101 | fn print(ast: MalType) -> String { 102 | pr_str(&ast, true) 103 | } 104 | 105 | fn eval_ast(ast: MalType, repl_env: &ReplEnv) -> MalResult { 106 | if let Some(symbol) = ast.symbol_val() { 107 | if let Some(val) = repl_env.get(symbol) { 108 | return Ok(val.to_owned()); 109 | } else { 110 | return Err(MalError::SymbolUndefined(symbol.to_string())); 111 | } 112 | } else if let Some(vec) = ast.list_or_vector_val() { 113 | let results: Result, MalError> = vec.into_iter() 114 | .map(|item| eval(item.clone(), repl_env)) 115 | .collect(); 116 | if ast.is_list() { 117 | return Ok(MalType::list(results?)); 118 | } else { 119 | return Ok(MalType::vector(results?)); 120 | } 121 | } else if let Some(map) = ast.hashmap_val() { 122 | let mut new_map = BTreeMap::new(); 123 | for (key, val) in map { 124 | new_map.insert(key.clone(), eval(val.clone(), repl_env)?); 125 | } 126 | let mut map = MalType::hashmap(new_map); 127 | return Ok(map); 128 | }; 129 | Ok(ast) 130 | } 131 | 132 | fn list_len(list: &MalType) -> usize { 133 | if let Some(vec) = list.list_or_vector_val() { 134 | vec.len() 135 | } else { 136 | panic!("Expected a list but got: {:?}", list) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /docs/TODO: -------------------------------------------------------------------------------- 1 | --------------------------------------------- 2 | 3 | General: 4 | - add chat bot for #mal 5 | - move tokenizer.mal and reader.mal from malc along with 6 | ./examples/{equality,memoize,pprint,protocols}.mal and 7 | ./core.mal to ./lib directory 8 | 9 | - Finish guide.md 10 | - mention that identifier names are suggested. some have run 11 | into collisions with READ,EVAL,PRINT in case insensitive 12 | languages 13 | - simplify: "X argument (list element Y)" -> ast[Y] 14 | - more clarity about when to peek and poke in read_list and 15 | read_form 16 | - tokenizer: use first group rather than whole match (to 17 | eliminate whitespace/commas) 18 | 19 | All Implementations: 20 | - regular expression matching in runtest 21 | - add re (use in rep) everywhere and use that (to avoid printing) 22 | - fix stepA soft failures: lua matlab miniMAL perl racket 23 | 24 | Other ideas for All: 25 | - propagate/print errors when self-hosted 26 | - redefine (defmacro!) as (def! (macro*)) 27 | - Fix/implement interop in more implementations 28 | 29 | - metadata on symbols (as per Clojure) 30 | - metadata as a map only. ^ merges metadata in the reader itself. 31 | Line numbers in metadata from reader. 32 | - protocols! 33 | - https://github.com/pixie-lang/pixie 34 | - http://www.toccata.io/2015/01/Mapping/ 35 | - namespaces 36 | - environments first class: *ENV*, *outer* defined by env-new 37 | - namespaces is *namespaces* map in environment which maps namespace 38 | names to other environments. 39 | - def! become an alias for (env-set! *ENV* 'sym value) 40 | - Namespace lookup: go up the environment hierarchy until 41 | a *namespaces* map is found with the namespace name being 42 | looked up. Then the symbol would be looked up starting in 43 | the namespace environment. Need protocols first probably. 44 | 45 | - multi-line REPL read 46 | - loop/recur ? 47 | - gensym reader inside quasiquote 48 | - standalone executable 49 | 50 | 51 | --------------------------------------------- 52 | 53 | Bash: 54 | - explore using ${!prefix*} syntax (more like make impl) 55 | - GC 56 | 57 | C: 58 | - come up with better way to do 20 vararg code 59 | 60 | C#: 61 | - accumulates line breaks with mal/clojurewest2014.mal 62 | - interop: http://www.ckode.dk/programming/eval-in-c-yes-its-possible/ 63 | 64 | CoffeeScript: 65 | - make target to compile to JS 66 | 67 | Go: 68 | - consider variable arguments in places where it makes sense 69 | https://gobyexample.com/variadic-functions 70 | 71 | Haskell: 72 | - TCO using seq/bang patterns: 73 | http://stackoverflow.com/questions/9149183/tail-optimization-guarantee-loop-encoding-in-haskell 74 | - immediately exits mal/clojurewest2014.mal ("\/" exception) 75 | 76 | Java: 77 | - build step, don't use mvn in run script 78 | - Use gradle instead of mvn 79 | http://blog.paralleluniverse.co/2014/05/01/modern-java/ 80 | 81 | Javascript: 82 | - interop: adopt techniques from miniMAL 83 | 84 | Make: 85 | - allow '_' in make variable names 86 | - hash-map with space in key string 87 | - errors should propagate up from within load-file 88 | - GC: explore using "undefine" directive in Make 3.82 89 | 90 | Mal: 91 | - line numbers in errors 92 | - step5_tco 93 | 94 | miniMAL: 95 | - figure out why {} literals are "static"/persistent 96 | 97 | ObjPascal: 98 | - verify that GC/reference counting works 99 | - fix comment by itself error at REPL 100 | 101 | plpgsql: 102 | - maybe combine wrap.sh and run 103 | 104 | Perl: 105 | - fix metadata on native functions 106 | - fix extra line breaks at REPL 107 | 108 | Postscript: 109 | - add negative numbers 110 | - fix blank line after comments 111 | - fix command line arg processing (doesn't run file specified) 112 | 113 | Powershell: 114 | - convert function with "abc_def" to "abc-def" 115 | - remove extraneous return statements at end of functions 116 | - remove unnecessary semi-colons 117 | - use ArrayList instead of Array for performance 118 | - new test to test Keys/keys as hash-map key 119 | - test *? predicates with nil 120 | 121 | R: 122 | - tracebacks in errors 123 | - fix running from different directory 124 | 125 | Racket 126 | - metadata on collections 127 | 128 | Rust: 129 | - fix 'make all' invocation of cargo build 130 | 131 | Scala 132 | - readline 133 | - fix exception when finished running something on command line 134 | 135 | VHDL: 136 | - combine run_vhdl.sh and run 137 | 138 | vimscript: 139 | - combine run_vimscript.sh and run 140 | -------------------------------------------------------------------------------- /runtest-old.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys, re 4 | import argparse 5 | 6 | # http://pexpect.sourceforge.net/pexpect.html 7 | from pexpect import spawn, EOF, TIMEOUT 8 | 9 | # TODO: do we need to support '\n' too 10 | sep = "\r\n" 11 | rundir = None 12 | 13 | parser = argparse.ArgumentParser( 14 | description="Run a test file against a Mal implementation") 15 | parser.add_argument('--rundir', 16 | help="change to the directory before running tests") 17 | parser.add_argument('--start-timeout', default=10, type=int, 18 | help="default timeout for initial prompt") 19 | parser.add_argument('--test-timeout', default=20, type=int, 20 | help="default timeout for each individual test action") 21 | parser.add_argument('--pre-eval', default=None, type=str, 22 | help="Mal code to evaluate prior to running the test") 23 | parser.add_argument('--redirect', action='store_true', 24 | help="Run implementation in bash and redirect output to /dev/null") 25 | 26 | parser.add_argument('test_file', type=argparse.FileType('r'), 27 | help="a test file formatted as with mal test data") 28 | parser.add_argument('mal_cmd', nargs="*", 29 | help="Mal implementation command line. Use '--' to " 30 | "specify a Mal command line with dashed options.") 31 | 32 | args = parser.parse_args(sys.argv[1:]) 33 | test_data = args.test_file.read().split('\n') 34 | 35 | if args.rundir: os.chdir(args.rundir) 36 | 37 | if args.redirect: 38 | # Redirect to try and force raw mode (no ASCII codes) 39 | p = spawn('/bin/bash -c "' + " ".join(args.mal_cmd) + ' |tee /dev/null"') 40 | else: 41 | p = spawn(args.mal_cmd[0], args.mal_cmd[1:]) 42 | 43 | 44 | test_idx = 0 45 | def read_test(data): 46 | global test_idx 47 | form, output, ret = None, "", None 48 | while data: 49 | test_idx += 1 50 | line = data.pop(0) 51 | if re.match(r"^\s*$", line): # blank line 52 | continue 53 | elif line[0:3] == ";;;": # ignore comment 54 | continue 55 | elif line[0:2] == ";;": # output comment 56 | print line[3:] 57 | continue 58 | elif line[0:2] == ";": # unexpected comment 59 | print "Test data error at line %d:\n%s" % (test_idx, line) 60 | return None, None, None, test_idx 61 | form = line # the line is a form to send 62 | 63 | # Now find the output and return value 64 | while data: 65 | line = data[0] 66 | if line[0:3] == ";=>": 67 | ret = line[3:].replace('\\r', '\r').replace('\\n', '\n') 68 | test_idx += 1 69 | data.pop(0) 70 | break 71 | elif line[0:2] == "; ": 72 | output = output + line[2:] + sep 73 | test_idx += 1 74 | data.pop(0) 75 | else: 76 | ret = "*" 77 | break 78 | if ret: break 79 | 80 | return form, output, ret, test_idx 81 | 82 | def assert_prompt(timeout): 83 | # Wait for the initial prompt 84 | idx = p.expect(['user> ', 'mal-user> ', EOF, TIMEOUT], 85 | timeout=timeout) 86 | if idx not in [0,1]: 87 | print "Did not get 'user> ' or 'mal-user> ' prompt" 88 | print " Got : %s" % repr(p.before) 89 | sys.exit(1) 90 | 91 | 92 | # Wait for the initial prompt 93 | assert_prompt(args.start_timeout) 94 | 95 | # Send the pre-eval code if any 96 | if args.pre_eval: 97 | sys.stdout.write("RUNNING pre-eval: %s" % args.pre_eval) 98 | p.sendline(args.pre_eval) 99 | assert_prompt(args.test_timeout) 100 | 101 | fail_cnt = 0 102 | 103 | while test_data: 104 | form, out, ret, line_num = read_test(test_data) 105 | if form == None: 106 | break 107 | sys.stdout.write("TEST: %s -> [%s,%s]" % (form, repr(out), repr(ret))) 108 | sys.stdout.flush() 109 | expected = "%s%s%s%s" % (form, sep, out, ret) 110 | 111 | p.sendline(form) 112 | try: 113 | idx = p.expect(['\r\nuser> ', '\nuser> ', 114 | '\r\nmal-user> ', '\nmal-user> '], 115 | timeout=args.test_timeout) 116 | #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after)) 117 | if ret == "*" or p.before == expected: 118 | print " -> SUCCESS" 119 | else: 120 | print " -> FAIL (line %d):" % line_num 121 | print " Expected : %s" % repr(expected) 122 | print " Got : %s" % repr(p.before) 123 | fail_cnt += 1 124 | except EOF: 125 | print "Got EOF" 126 | sys.exit(1) 127 | except TIMEOUT: 128 | print "Got TIMEOUT, received: %s" % repr(p.before) 129 | sys.exit(1) 130 | 131 | if fail_cnt > 0: 132 | print "FAILURES: %d" % fail_cnt 133 | sys.exit(2) 134 | sys.exit(0) 135 | -------------------------------------------------------------------------------- /tests/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # WARNING: This file is deprecated. Each implementation now has its 2 | # own Dockerfile. 3 | 4 | FROM ubuntu:utopic 5 | MAINTAINER Joel Martin 6 | 7 | ENV DEBIAN_FRONTEND noninteractive 8 | 9 | RUN echo "deb http://dl.bintray.com/sbt/debian /" > /etc/apt/sources.list.d/sbt.list 10 | RUN apt-get -y update 11 | 12 | # 13 | # General dependencies 14 | # 15 | VOLUME /mal 16 | 17 | RUN apt-get -y install make wget curl git 18 | 19 | # Deps for compiled languages (C, Go, Rust, Nim, etc) 20 | RUN apt-get -y install gcc pkg-config 21 | 22 | # Deps for Java-based languages (Clojure, Scala, Java) 23 | RUN apt-get -y install openjdk-7-jdk 24 | ENV MAVEN_OPTS -Duser.home=/mal 25 | 26 | # Deps for Mono-based languages (C#, VB.Net) 27 | RUN apt-get -y install mono-runtime mono-mcs mono-vbnc 28 | 29 | # Deps for node.js languages (JavaScript, CoffeeScript, miniMAL, etc) 30 | RUN apt-get -y install nodejs npm 31 | RUN ln -sf nodejs /usr/bin/node 32 | 33 | 34 | # 35 | # Implementation specific installs 36 | # 37 | 38 | # GNU awk 39 | RUN apt-get -y install gawk 40 | 41 | # Bash 42 | RUN apt-get -y install bash 43 | 44 | # C 45 | RUN apt-get -y install libglib2.0 libglib2.0-dev 46 | RUN apt-get -y install libffi-dev libreadline-dev libedit2 libedit-dev 47 | 48 | # C++ 49 | RUN apt-get -y install g++-4.9 libreadline-dev 50 | 51 | # Clojure 52 | ADD https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein \ 53 | /usr/local/bin/lein 54 | RUN sudo chmod 0755 /usr/local/bin/lein 55 | ENV LEIN_HOME /mal/.lein 56 | ENV LEIN_JVM_OPTS -Duser.home=/mal 57 | 58 | # CoffeeScript 59 | RUN npm install -g coffee-script 60 | RUN touch /.coffee_history && chmod go+w /.coffee_history 61 | 62 | # C# 63 | RUN apt-get -y install mono-mcs 64 | 65 | # Elixir 66 | RUN wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb \ 67 | && dpkg -i erlang-solutions_1.0_all.deb 68 | RUN apt-get update 69 | RUN apt-get -y install elixir 70 | 71 | # Erlang R17 (so I can use maps) 72 | RUN apt-get -y install build-essential libncurses5-dev libssl-dev 73 | RUN cd /tmp && wget http://www.erlang.org/download/otp_src_17.5.tar.gz \ 74 | && tar -C /tmp -zxf /tmp/otp_src_17.5.tar.gz \ 75 | && cd /tmp/otp_src_17.5 && ./configure && make && make install \ 76 | && rm -rf /tmp/otp_src_17.5 /tmp/otp_src_17.5.tar.gz 77 | # Rebar for building the Erlang implementation 78 | RUN cd /tmp/ && git clone -q https://github.com/rebar/rebar.git \ 79 | && cd /tmp/rebar && ./bootstrap && cp rebar /usr/local/bin \ 80 | && rm -rf /tmp/rebar 81 | 82 | # Forth 83 | RUN apt-get -y install gforth 84 | 85 | # Go 86 | RUN apt-get -y install golang 87 | 88 | # Guile 89 | RUN apt-get -y install libunistring-dev libgc-dev autoconf libtool flex gettext texinfo libgmp-dev 90 | RUN git clone git://git.sv.gnu.org/guile.git /tmp/guile \ 91 | && cd /tmp/guile && ./autogen.sh && ./configure && make && make install 92 | 93 | # Haskell 94 | RUN apt-get -y install ghc haskell-platform libghc-readline-dev libghc-editline-dev 95 | 96 | # Java 97 | RUN apt-get -y install maven2 98 | 99 | # JavaScript 100 | # Already satisfied above 101 | 102 | # Julia 103 | RUN apt-get -y install software-properties-common 104 | RUN apt-add-repository -y ppa:staticfloat/juliareleases 105 | RUN apt-get -y update 106 | RUN apt-get -y install julia 107 | 108 | # Lua 109 | RUN apt-get -y install lua5.1 lua-rex-pcre luarocks 110 | RUN luarocks install linenoise 111 | 112 | # Mal 113 | # N/A: self-hosted on other language implementations 114 | 115 | # GNU Make 116 | # Already satisfied as a based dependency for testing 117 | 118 | # miniMAL 119 | RUN npm install -g minimal-lisp 120 | 121 | # Nim 122 | RUN cd /tmp && wget http://nim-lang.org/download/nim-0.17.0.tar.xz \ 123 | && tar xvJf /tmp/nim-0.17.0.tar.xz && cd nim-0.17.0 \ 124 | && make && sh install.sh /usr/local/bin \ 125 | && rm -r /tmp/nim-0.17.0 126 | 127 | # OCaml 128 | RUN apt-get -y install ocaml-batteries-included 129 | 130 | # perl 131 | RUN apt-get -y install perl 132 | 133 | # PHP 134 | RUN apt-get -y install php5-cli 135 | 136 | # PostScript/ghostscript 137 | RUN apt-get -y install ghostscript 138 | 139 | # python 140 | RUN apt-get -y install python 141 | 142 | # R 143 | RUN apt-get -y install r-base-core 144 | 145 | # Racket 146 | RUN apt-get -y install racket 147 | 148 | # Ruby 149 | RUN apt-get -y install ruby 150 | 151 | # Rust 152 | RUN curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh 153 | 154 | # Scala 155 | RUN apt-get -y --force-yes install sbt 156 | RUN apt-get -y install scala 157 | ENV SBT_OPTS -Duser.home=/mal 158 | 159 | # VB.Net 160 | RUN apt-get -y install mono-vbnc 161 | 162 | # TODO: move up 163 | # Factor 164 | RUN apt-get -y install libgtkglext1 165 | RUN cd /usr/lib/x86_64-linux-gnu/ \ 166 | && wget http://downloads.factorcode.org/releases/0.97/factor-linux-x86-64-0.97.tar.gz \ 167 | && tar xvzf factor-linux-x86-64-0.97.tar.gz \ 168 | && ln -sf /usr/lib/x86_64-linux-gnu/factor/factor /usr/bin/factor \ 169 | && rm factor-linux-x86-64-0.97.tar.gz 170 | 171 | # MATLAB is proprietary/licensed. Maybe someday with Octave. 172 | # Swift is XCode/OS X only 173 | ENV SKIP_IMPLS matlab swift 174 | 175 | ENV DEBIAN_FRONTEND newt 176 | ENV HOME / 177 | 178 | WORKDIR /mal 179 | -------------------------------------------------------------------------------- /mal/step8_macros.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | (load-file "../mal/core.mal") 3 | 4 | ;; read 5 | (def! READ (fn* [strng] 6 | (read-string strng))) 7 | 8 | 9 | ;; eval 10 | (def! is-pair (fn* [x] 11 | (if (sequential? x) 12 | (if (> (count x) 0) 13 | true)))) 14 | 15 | (def! QUASIQUOTE (fn* [ast] 16 | (cond 17 | (not (is-pair ast)) 18 | (list 'quote ast) 19 | 20 | (= 'unquote (first ast)) 21 | (nth ast 1) 22 | 23 | (if (is-pair (first ast)) 24 | (if (= 'splice-unquote (first (first ast))) 25 | true)) 26 | (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) 27 | 28 | "else" 29 | (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) 30 | 31 | (def! is-macro-call (fn* [ast env] 32 | (if (list? ast) 33 | (let* [a0 (first ast)] 34 | (if (symbol? a0) 35 | (if (env-find env a0) 36 | (let* [m (meta (env-get env a0))] 37 | (if m 38 | (if (get m "ismacro") 39 | true))))))))) 40 | 41 | (def! MACROEXPAND (fn* [ast env] 42 | (if (is-macro-call ast env) 43 | (let* [mac (env-get env (first ast))] 44 | (MACROEXPAND (apply mac (rest ast)) env)) 45 | ast))) 46 | 47 | (def! eval-ast (fn* [ast env] (do 48 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 49 | (cond 50 | (symbol? ast) (env-get env ast) 51 | 52 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 53 | 54 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 55 | 56 | (map? ast) (apply hash-map 57 | (apply concat 58 | (map (fn* [k] [k (EVAL (get ast k) env)]) 59 | (keys ast)))) 60 | 61 | "else" ast)))) 62 | 63 | (def! LET (fn* [env args] 64 | (if (> (count args) 0) 65 | (do 66 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 67 | (LET env (rest (rest args))))))) 68 | 69 | (def! EVAL (fn* [ast env] (do 70 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 71 | (if (not (list? ast)) 72 | (eval-ast ast env) 73 | 74 | ;; apply list 75 | (let* [ast (MACROEXPAND ast env)] 76 | (if (not (list? ast)) 77 | (eval-ast ast env) 78 | 79 | (let* [a0 (first ast)] 80 | (cond 81 | (nil? a0) 82 | ast 83 | 84 | (= 'def! a0) 85 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 86 | 87 | (= 'let* a0) 88 | (let* [let-env (new-env env)] 89 | (do 90 | (LET let-env (nth ast 1)) 91 | (EVAL (nth ast 2) let-env))) 92 | 93 | (= 'quote a0) 94 | (nth ast 1) 95 | 96 | (= 'quasiquote a0) 97 | (let* [a1 (nth ast 1)] 98 | (EVAL (QUASIQUOTE a1) env)) 99 | 100 | (= 'defmacro! a0) 101 | (let* [a1 (nth ast 1) 102 | a2 (nth ast 2) 103 | f (EVAL a2 env) 104 | m (or (meta f) {}) 105 | mac (with-meta f (assoc m "ismacro" true))] 106 | (env-set env a1 mac)) 107 | 108 | (= 'macroexpand a0) 109 | (let* [a1 (nth ast 1)] 110 | (MACROEXPAND a1 env)) 111 | 112 | (= 'do a0) 113 | (let* [el (eval-ast (rest ast) env)] 114 | (nth el (- (count el) 1))) 115 | 116 | (= 'if a0) 117 | (let* [cond (EVAL (nth ast 1) env)] 118 | (if (or (= cond nil) (= cond false)) 119 | (if (> (count ast) 3) 120 | (EVAL (nth ast 3) env) 121 | nil) 122 | (EVAL (nth ast 2) env))) 123 | 124 | (= 'fn* a0) 125 | (fn* [& args] 126 | (EVAL (nth ast 2) (new-env env (nth ast 1) args))) 127 | 128 | "else" 129 | (let* [el (eval-ast ast env) 130 | f (first el) 131 | args (rest el)] 132 | (apply f args)))))))))) 133 | 134 | 135 | ;; print 136 | (def! PRINT (fn* [exp] (pr-str exp))) 137 | 138 | ;; repl 139 | (def! repl-env (new-env)) 140 | (def! rep (fn* [strng] 141 | (PRINT (EVAL (READ strng) repl-env)))) 142 | 143 | ;; core.mal: defined directly using mal 144 | (map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns) 145 | (env-set repl-env 'eval (fn* [ast] (EVAL ast repl-env))) 146 | (env-set repl-env '*ARGV* (rest *ARGV*)) 147 | 148 | ;; core.mal: defined using the new language itself 149 | (rep "(def! not (fn* [a] (if a false true)))") 150 | (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") 151 | (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)))))))") 152 | (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))))))))") 153 | 154 | ;; repl loop 155 | (def! repl-loop (fn* [] 156 | (let* [line (readline "mal-user> ")] 157 | (if line 158 | (do 159 | (if (not (= "" line)) 160 | (try* 161 | (println (rep line)) 162 | (catch* exc 163 | (println "Uncaught exception:" exc)))) 164 | (repl-loop)))))) 165 | 166 | (def! -main (fn* [& args] 167 | (if (> (count args) 0) 168 | (rep (str "(load-file \"" (first args) "\")")) 169 | (repl-loop)))) 170 | (apply -main *ARGV*) 171 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | # matrix layout based on: 4 | # https://github.com/libressl-portable/portable/blob/9e090286b55def5ca2c0cc375c65023a70d8796e/.travis.yml 5 | 6 | matrix: 7 | include: 8 | - {env: IMPL=ada, services: [docker]} 9 | - {env: IMPL=awk, services: [docker]} 10 | - {env: IMPL=bash, services: [docker]} 11 | - {env: IMPL=basic basic_MODE=cbm, services: [docker]} 12 | - {env: IMPL=basic basic_MODE=qbasic, services: [docker]} 13 | - {env: IMPL=c, services: [docker]} 14 | - {env: IMPL=cpp, services: [docker]} 15 | - {env: IMPL=coffee, services: [docker]} 16 | - {env: IMPL=cs, services: [docker]} 17 | - {env: IMPL=chuck, services: [docker]} 18 | - {env: IMPL=clojure clojure_MODE=clj, services: [docker]} 19 | - {env: IMPL=clojure clojure_MODE=cljs, services: [docker]} 20 | - {env: IMPL=common-lisp, services: [docker]} 21 | - {env: IMPL=crystal, services: [docker]} 22 | - {env: IMPL=d, services: [docker]} 23 | - {env: IMPL=dart, services: [docker]} 24 | - {env: IMPL=elisp, services: [docker]} 25 | - {env: IMPL=elixir, services: [docker]} 26 | - {env: IMPL=elm, services: [docker]} 27 | - {env: IMPL=erlang NO_PERF=1, services: [docker]} # perf runs out of memory 28 | - {env: IMPL=es6, services: [docker]} 29 | - {env: IMPL=factor, services: [docker]} 30 | - {env: IMPL=fantom, services: [docker]} 31 | - {env: IMPL=forth, services: [docker]} 32 | - {env: IMPL=fsharp, services: [docker]} 33 | - {env: IMPL=go, services: [docker]} 34 | - {env: IMPL=groovy, services: [docker]} 35 | - {env: IMPL=gst, services: [docker]} 36 | - {env: IMPL=guile, services: [docker]} 37 | - {env: IMPL=haskell, services: [docker]} 38 | - {env: IMPL=haxe haxe_MODE=neko, services: [docker]} 39 | - {env: IMPL=haxe haxe_MODE=python, services: [docker]} 40 | - {env: IMPL=haxe haxe_MODE=cpp, services: [docker]} 41 | - {env: IMPL=haxe haxe_MODE=js, services: [docker]} 42 | - {env: IMPL=hy, services: [docker]} 43 | - {env: IMPL=io, services: [docker]} 44 | - {env: IMPL=java, services: [docker]} 45 | - {env: IMPL=js, services: [docker]} 46 | - {env: IMPL=julia, services: [docker]} 47 | - {env: IMPL=kotlin, services: [docker]} 48 | - {env: IMPL=livescript, services: [docker]} 49 | - {env: IMPL=logo, services: [docker]} 50 | - {env: IMPL=lua, services: [docker]} 51 | - {env: IMPL=make, services: [docker]} 52 | - {env: IMPL=mal BUILD_IMPL=js NO_PERF=1, services: [docker]} 53 | - {env: IMPL=matlab, services: [docker]} # Uses Octave 54 | - {env: IMPL=miniMAL, services: [docker]} 55 | - {env: IMPL=nasm, services: [docker]} 56 | - {env: IMPL=nim, services: [docker]} 57 | - {env: IMPL=objpascal, services: [docker]} 58 | - {env: IMPL=objc NO_DOCKER=1, os: osx, osx_image: xcode7} 59 | - {env: IMPL=objc, services: [docker]} 60 | - {env: IMPL=ocaml, services: [docker]} 61 | - {env: IMPL=perl, services: [docker]} 62 | - {env: IMPL=perl6, services: [docker]} 63 | - {env: IMPL=php, services: [docker]} 64 | - {env: IMPL=pil, services: [docker]} 65 | - {env: IMPL=plpgsql, services: [docker]} 66 | # - {env: IMPL=plsql, services: [docker]} 67 | - {env: IMPL=ps, services: [docker]} 68 | - {env: IMPL=powershell, services: [docker]} 69 | - {env: IMPL=python python_MODE=python2, services: [docker]} 70 | - {env: IMPL=python python_MODE=python3, services: [docker]} 71 | - {env: IMPL=r, services: [docker]} 72 | - {env: IMPL=racket, services: [docker]} 73 | - {env: IMPL=rexx, services: [docker]} 74 | - {env: IMPL=rpython, services: [docker]} 75 | - {env: IMPL=ruby, services: [docker]} 76 | - {env: IMPL=rust, services: [docker]} 77 | - {env: IMPL=scala, services: [docker]} 78 | - {env: IMPL=scheme scheme_MODE=chibi, services: [docker]} 79 | - {env: IMPL=scheme scheme_MODE=kawa, services: [docker]} 80 | - {env: IMPL=scheme scheme_MODE=gauche, services: [docker]} 81 | - {env: IMPL=scheme scheme_MODE=chicken, services: [docker]} 82 | - {env: IMPL=scheme scheme_MODE=sagittarius, services: [docker]} 83 | - {env: IMPL=scheme scheme_MODE=cyclone, services: [docker]} 84 | # - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} 85 | - {env: IMPL=skew, services: [docker]} 86 | - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7.3} 87 | - {env: IMPL=swift3, services: [docker]} 88 | - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8} 89 | - {env: IMPL=tcl, services: [docker]} 90 | - {env: IMPL=ts, services: [docker]} 91 | - {env: IMPL=vb, services: [docker]} 92 | - {env: IMPL=vhdl, services: [docker]} 93 | - {env: IMPL=vimscript, services: [docker]} 94 | - {env: IMPL=yorick, services: [docker]} 95 | 96 | script: 97 | # Build 98 | - ./.travis_build.sh 99 | 100 | # Regular tests 101 | - ./.travis_test.sh test ${IMPL} 102 | - cat test.err || true 103 | 104 | # NOTE: use self-host-test branch 105 | # Self-hosted tests 106 | #- ./.travis_test.sh test mal ${IMPL} 107 | #- cat test.err || true; rm -f test.err 108 | 109 | # Performance tests 110 | - if [ -z "${NO_PERF}" ]; then ./.travis_test.sh perf ${IMPL}; fi 111 | - cat perf.err || true 112 | -------------------------------------------------------------------------------- /tests/stepA_mal.mal: -------------------------------------------------------------------------------- 1 | ;;; 2 | ;;; See IMPL/tests/stepA_mal.mal for implementation specific 3 | ;;; interop tests. 4 | ;;; 5 | 6 | 7 | ;; 8 | ;; Testing readline 9 | (readline "mal-user> ") 10 | "hello" 11 | ;=>"\"hello\"" 12 | 13 | ;; 14 | ;; Testing *host-language* 15 | ;;; each impl is different, but this should return false 16 | ;;; rather than throwing an exception 17 | (= "something bogus" *host-language*) 18 | ;=>false 19 | 20 | 21 | ;>>> deferrable=True 22 | ;; 23 | ;; ------- Deferrable Functionality ---------- 24 | ;; ------- (Needed for self-hosting) ------- 25 | 26 | ;; 27 | ;; Testing metadata on functions 28 | 29 | ;; 30 | ;; Testing metadata on mal functions 31 | 32 | (meta (fn* (a) a)) 33 | ;=>nil 34 | 35 | (meta (with-meta (fn* (a) a) {"b" 1})) 36 | ;=>{"b" 1} 37 | 38 | (meta (with-meta (fn* (a) a) "abc")) 39 | ;=>"abc" 40 | 41 | (def! l-wm (with-meta (fn* (a) a) {"b" 2})) 42 | (meta l-wm) 43 | ;=>{"b" 2} 44 | 45 | (meta (with-meta l-wm {"new_meta" 123})) 46 | ;=>{"new_meta" 123} 47 | (meta l-wm) 48 | ;=>{"b" 2} 49 | 50 | (def! f-wm (with-meta (fn* [a] (+ 1 a)) {"abc" 1})) 51 | (meta f-wm) 52 | ;=>{"abc" 1} 53 | 54 | (meta (with-meta f-wm {"new_meta" 123})) 55 | ;=>{"new_meta" 123} 56 | (meta f-wm) 57 | ;=>{"abc" 1} 58 | 59 | (def! f-wm2 ^{"abc" 1} (fn* [a] (+ 1 a))) 60 | (meta f-wm2) 61 | ;=>{"abc" 1} 62 | 63 | ;; Meta of native functions should return nil (not fail) 64 | (meta +) 65 | ;=>nil 66 | 67 | 68 | ;; 69 | ;; Make sure closures and metadata co-exist 70 | (def! gen-plusX (fn* (x) (with-meta (fn* (b) (+ x b)) {"meta" 1}))) 71 | (def! plus7 (gen-plusX 7)) 72 | (def! plus8 (gen-plusX 8)) 73 | (plus7 8) 74 | ;=>15 75 | (meta plus7) 76 | ;=>{"meta" 1} 77 | (meta plus8) 78 | ;=>{"meta" 1} 79 | (meta (with-meta plus7 {"meta" 2})) 80 | ;=>{"meta" 2} 81 | (meta plus8) 82 | ;=>{"meta" 1} 83 | 84 | ;; 85 | ;; Testing hash-map evaluation and atoms (i.e. an env) 86 | (def! e (atom {"+" +})) 87 | (swap! e assoc "-" -) 88 | ( (get @e "+") 7 8) 89 | ;=>15 90 | ( (get @e "-") 11 8) 91 | ;=>3 92 | (swap! e assoc "foo" (list)) 93 | (get @e "foo") 94 | ;=>() 95 | (swap! e assoc "bar" '(1 2 3)) 96 | (get @e "bar") 97 | ;=>(1 2 3) 98 | 99 | ;; ------------------------------------------------------------------ 100 | 101 | ;>>> soft=True 102 | ;>>> optional=True 103 | ;; 104 | ;; ------- Optional Functionality -------------- 105 | ;; ------- (Not needed for self-hosting) ------- 106 | 107 | ;; 108 | ;; Testing string? function 109 | (string? "") 110 | ;=>true 111 | (string? 'abc) 112 | ;=>false 113 | (string? "abc") 114 | ;=>true 115 | (string? :abc) 116 | ;=>false 117 | (string? (keyword "abc")) 118 | ;=>false 119 | (string? 234) 120 | ;=>false 121 | (string? nil) 122 | ;=>false 123 | 124 | ;; Testing number? function 125 | (number? 123) 126 | ;=>true 127 | (number? -1) 128 | ;=>true 129 | (number? nil) 130 | ;=>false 131 | (number? false) 132 | ;=>false 133 | (number? "123") 134 | ;=>false 135 | 136 | (def! add1 (fn* (x) (+ x 1))) 137 | 138 | ;; Testing fn? function 139 | (fn? +) 140 | ;=>true 141 | (fn? add1) 142 | ;=>true 143 | (fn? cond) 144 | ;=>false 145 | (fn? "+") 146 | ;=>false 147 | (fn? :+) 148 | ;=>false 149 | 150 | ;; Testing macro? function 151 | (macro? cond) 152 | ;=>true 153 | (macro? +) 154 | ;=>false 155 | (macro? add1) 156 | ;=>false 157 | (macro? "+") 158 | ;=>false 159 | (macro? :+) 160 | ;=>false 161 | 162 | 163 | ;; 164 | ;; Testing conj function 165 | (conj (list) 1) 166 | ;=>(1) 167 | (conj (list 1) 2) 168 | ;=>(2 1) 169 | (conj (list 2 3) 4) 170 | ;=>(4 2 3) 171 | (conj (list 2 3) 4 5 6) 172 | ;=>(6 5 4 2 3) 173 | (conj (list 1) (list 2 3)) 174 | ;=>((2 3) 1) 175 | 176 | (conj [] 1) 177 | ;=>[1] 178 | (conj [1] 2) 179 | ;=>[1 2] 180 | (conj [2 3] 4) 181 | ;=>[2 3 4] 182 | (conj [2 3] 4 5 6) 183 | ;=>[2 3 4 5 6] 184 | (conj [1] [2 3]) 185 | ;=>[1 [2 3]] 186 | 187 | ;; 188 | ;; Testing seq function 189 | (seq "abc") 190 | ;=>("a" "b" "c") 191 | (apply str (seq "this is a test")) 192 | ;=>"this is a test" 193 | (seq '(2 3 4)) 194 | ;=>(2 3 4) 195 | (seq [2 3 4]) 196 | ;=>(2 3 4) 197 | 198 | (seq "") 199 | ;=>nil 200 | (seq '()) 201 | ;=>nil 202 | (seq []) 203 | ;=>nil 204 | (seq nil) 205 | ;=>nil 206 | 207 | ;; 208 | ;; Testing metadata on collections 209 | 210 | (meta [1 2 3]) 211 | ;=>nil 212 | 213 | (with-meta [1 2 3] {"a" 1}) 214 | ;=>[1 2 3] 215 | 216 | (meta (with-meta [1 2 3] {"a" 1})) 217 | ;=>{"a" 1} 218 | 219 | (vector? (with-meta [1 2 3] {"a" 1})) 220 | ;=>true 221 | 222 | (meta (with-meta [1 2 3] "abc")) 223 | ;=>"abc" 224 | 225 | (meta (with-meta (list 1 2 3) {"a" 1})) 226 | ;=>{"a" 1} 227 | 228 | (list? (with-meta (list 1 2 3) {"a" 1})) 229 | ;=>true 230 | 231 | (meta (with-meta {"abc" 123} {"a" 1})) 232 | ;=>{"a" 1} 233 | 234 | (map? (with-meta {"abc" 123} {"a" 1})) 235 | ;=>true 236 | 237 | ;;; Not actually supported by Clojure 238 | ;;;(meta (with-meta (atom 7) {"a" 1})) 239 | ;;;;=>{"a" 1} 240 | 241 | (def! l-wm (with-meta [4 5 6] {"b" 2})) 242 | ;=>[4 5 6] 243 | (meta l-wm) 244 | ;=>{"b" 2} 245 | 246 | (meta (with-meta l-wm {"new_meta" 123})) 247 | ;=>{"new_meta" 123} 248 | (meta l-wm) 249 | ;=>{"b" 2} 250 | 251 | ;; 252 | ;; Testing metadata on builtin functions 253 | (meta +) 254 | ;=>nil 255 | (def! f-wm3 ^{"def" 2} +) 256 | (meta f-wm3) 257 | ;=>{"def" 2} 258 | (meta +) 259 | ;=>nil 260 | 261 | ;; 262 | ;; Testing gensym and clean or macro 263 | (= (gensym) (gensym)) 264 | ;=>false 265 | (let* [or_FIXME 23] (or false (+ or_FIXME 100))) 266 | ;=>123 267 | 268 | ;; 269 | ;; Testing time-ms function 270 | (def! start-time (time-ms)) 271 | (> start-time 0) 272 | ;=>true 273 | (let* [sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))] (sumdown 10)) ; Waste some time 274 | ;=>55 275 | (> (time-ms) start-time) 276 | ;=>true 277 | -------------------------------------------------------------------------------- /mal/step9_try.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | (load-file "../mal/core.mal") 3 | 4 | ;; read 5 | (def! READ (fn* [strng] 6 | (read-string strng))) 7 | 8 | 9 | ;; eval 10 | (def! is-pair (fn* [x] 11 | (if (sequential? x) 12 | (if (> (count x) 0) 13 | true)))) 14 | 15 | (def! QUASIQUOTE (fn* [ast] 16 | (cond 17 | (not (is-pair ast)) 18 | (list 'quote ast) 19 | 20 | (= 'unquote (first ast)) 21 | (nth ast 1) 22 | 23 | (if (is-pair (first ast)) 24 | (if (= 'splice-unquote (first (first ast))) 25 | true)) 26 | (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) 27 | 28 | "else" 29 | (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) 30 | 31 | (def! is-macro-call (fn* [ast env] 32 | (if (list? ast) 33 | (let* [a0 (first ast)] 34 | (if (symbol? a0) 35 | (if (env-find env a0) 36 | (let* [m (meta (env-get env a0))] 37 | (if m 38 | (if (get m "ismacro") 39 | true))))))))) 40 | 41 | (def! MACROEXPAND (fn* [ast env] 42 | (if (is-macro-call ast env) 43 | (let* [mac (env-get env (first ast))] 44 | (MACROEXPAND (apply mac (rest ast)) env)) 45 | ast))) 46 | 47 | (def! eval-ast (fn* [ast env] (do 48 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 49 | (cond 50 | (symbol? ast) (env-get env ast) 51 | 52 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 53 | 54 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 55 | 56 | (map? ast) (apply hash-map 57 | (apply concat 58 | (map (fn* [k] [k (EVAL (get ast k) env)]) 59 | (keys ast)))) 60 | 61 | "else" ast)))) 62 | 63 | (def! LET (fn* [env args] 64 | (if (> (count args) 0) 65 | (do 66 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 67 | (LET env (rest (rest args))))))) 68 | 69 | (def! EVAL (fn* [ast env] (do 70 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 71 | (if (not (list? ast)) 72 | (eval-ast ast env) 73 | 74 | ;; apply list 75 | (let* [ast (MACROEXPAND ast env)] 76 | (if (not (list? ast)) 77 | (eval-ast ast env) 78 | 79 | (let* [a0 (first ast)] 80 | (cond 81 | (nil? a0) 82 | ast 83 | 84 | (= 'def! a0) 85 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 86 | 87 | (= 'let* a0) 88 | (let* [let-env (new-env env)] 89 | (do 90 | (LET let-env (nth ast 1)) 91 | (EVAL (nth ast 2) let-env))) 92 | 93 | (= 'quote a0) 94 | (nth ast 1) 95 | 96 | (= 'quasiquote a0) 97 | (let* [a1 (nth ast 1)] 98 | (EVAL (QUASIQUOTE a1) env)) 99 | 100 | (= 'defmacro! a0) 101 | (let* [a1 (nth ast 1) 102 | a2 (nth ast 2) 103 | f (EVAL a2 env) 104 | m (or (meta f) {}) 105 | mac (with-meta f (assoc m "ismacro" true))] 106 | (env-set env a1 mac)) 107 | 108 | (= 'macroexpand a0) 109 | (let* [a1 (nth ast 1)] 110 | (MACROEXPAND a1 env)) 111 | 112 | (= 'try* a0) 113 | (if (= 'catch* (nth (nth ast 2) 0)) 114 | (try* 115 | (EVAL (nth ast 1) env) 116 | (catch* exc 117 | (EVAL (nth (nth ast 2) 2) 118 | (new-env env 119 | [(nth (nth ast 2)1)] 120 | [exc])))) 121 | (EVAL (nth ast 1) env)) 122 | 123 | (= 'do a0) 124 | (let* [el (eval-ast (rest ast) env)] 125 | (nth el (- (count el) 1))) 126 | 127 | (= 'if a0) 128 | (let* [cond (EVAL (nth ast 1) env)] 129 | (if (or (= cond nil) (= cond false)) 130 | (if (> (count ast) 3) 131 | (EVAL (nth ast 3) env) 132 | nil) 133 | (EVAL (nth ast 2) env))) 134 | 135 | (= 'fn* a0) 136 | (fn* [& args] 137 | (EVAL (nth ast 2) (new-env env (nth ast 1) args))) 138 | 139 | "else" 140 | (let* [el (eval-ast ast env) 141 | f (first el) 142 | args (rest el)] 143 | (apply f args)))))))))) 144 | 145 | 146 | ;; print 147 | (def! PRINT (fn* [exp] (pr-str exp))) 148 | 149 | ;; repl 150 | (def! repl-env (new-env)) 151 | (def! rep (fn* [strng] 152 | (PRINT (EVAL (READ strng) repl-env)))) 153 | 154 | ;; core.mal: defined directly using mal 155 | (map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns) 156 | (env-set repl-env 'eval (fn* [ast] (EVAL ast repl-env))) 157 | (env-set repl-env '*ARGV* (rest *ARGV*)) 158 | 159 | ;; core.mal: defined using the new language itself 160 | (rep "(def! not (fn* [a] (if a false true)))") 161 | (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") 162 | (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)))))))") 163 | (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))))))))") 164 | 165 | ;; repl loop 166 | (def! repl-loop (fn* [] 167 | (let* [line (readline "mal-user> ")] 168 | (if line 169 | (do 170 | (if (not (= "" line)) 171 | (try* 172 | (println (rep line)) 173 | (catch* exc 174 | (println "Uncaught exception:" exc)))) 175 | (repl-loop)))))) 176 | 177 | (def! -main (fn* [& args] 178 | (if (> (count args) 0) 179 | (rep (str "(load-file \"" (first args) "\")")) 180 | (repl-loop)))) 181 | (apply -main *ARGV*) 182 | -------------------------------------------------------------------------------- /docs/Hints.md: -------------------------------------------------------------------------------- 1 | # Mal/Make-a-Lisp Implementation Hints 2 | 3 | 4 | 5 | ### How do I get milliseconds since epoch for the "time-ms" function? 6 | ### Does the "time-ms" function have to return millisecond since epoch? 7 | 8 | Most languages usually have some way to do this natively even though 9 | it might be buried deeply in the language. If you are having trouble 10 | finding how to do this in your target language, consider asking the 11 | question on stackoverflow (if it has not been asked already) or asking 12 | on a discussion channel for your language because there is a good 13 | chance somebody there knows how and will answer quickly (if there is 14 | a native way at all). 15 | 16 | As a last resort you can always shell out and call the date command 17 | like this: 18 | 19 | ``` 20 | date +%s%3N 21 | ``` 22 | 23 | There are currently two implementations where this method was 24 | necessary (probably): bash and make. Unfortunately this method is 25 | limited to Linux/UNIX. 26 | 27 | Also, "time-ms" technically just needs to return accurate milliseconds 28 | since some arbitrary point in time (even program start) in order to be 29 | used correctly for timing/benchmarking. For consistency it is best if 30 | it returns epoch milliseconds, but this is not strictly required if 31 | you language limitations make it difficult (e.g. size limit of 32 | integers). 33 | 34 | 35 | 36 | 37 | ### How do I implement core/native functions if my language does not have any sort of function references (function pointers, closures, lambdas, etc)? 38 | ### How do I implement mal functions in step4 if I do not have function references? 39 | 40 | There are very few language that do not have any sort of function 41 | references so I suggest asking about the specific problem you are 42 | having on stackoverflow or a discussion channel for your language. In 43 | the rare case where you have a language without some sort of function 44 | reference abstraction, then you may have to implement a single 45 | function with a large switch statement (or equivalent) that calls out 46 | to the appropriate native core function ("+", "list", "throw", etc). 47 | In other words, you create a function that implements "function 48 | references" rather than using a feature of your language. You will 49 | still need to store the symbol names for those function in the base 50 | REPL environment but you will have some sort of tagging or marker that 51 | will indicate to the `EVAL` function that it should call your "big 52 | switch" function. 53 | 54 | In addition, if your language has no sort of closure/anonymous 55 | function capability (note that with sufficient object oriented 56 | features you can implement closure like functionality), then in step4 57 | you will need to borrow the way that functions are implemented from 58 | step5. In other words, functions become a normal data type that stores 59 | the function body (AST), the parameter list and the environment at the 60 | time the function is defined. When the function is invoked, `EVAL` 61 | will then evaluate these stored items rather than invoking a function 62 | closure. It is less convenient to have to do this at step4, but the 63 | bright side is that step5 will be simpler because you just have to 64 | implement the TCO loop because you have already refactored how 65 | functions are stored in step4. 66 | 67 | 68 | 69 | ### How do I implement terminal input and output in a language which does not have standard I/O capabilities? 70 | 71 | If your target language has some way to get data in and out while it 72 | is running (even if it is not standard terminal or file I/O) then you 73 | will need to create some sort of wrapper script (see 74 | `vimscript/run_vimscript.sh`) or call out to a shell script (see 75 | `make/readline.mk` and `make/util.mk`) or implement some other 76 | "appropriate" hack to to get the data in and out. As long 77 | as your implementation can be used with the test runner and the hack 78 | is just for working around I/O limitations in your target language, 79 | it is considered legitimate for upstream inclusion. 80 | 81 | ### How do I read the command-line arguments if my language runtime doesn't support access to them? 82 | 83 | Most languages give access to the command-line arguments that were passed to 84 | the program, either as an argument to the `main` function (like `argc` and 85 | `argv` in C) or as a global variable (like `sys.argv` in Python). If your 86 | target language doesn't have such mechanisms, consider adding a wrapper script 87 | that will read the command-line arguments that were passed to the script and 88 | pass them to the program in a way that the program can read. This might be 89 | through an environment variable (if the target language allows reading from 90 | environment variables) or through a temporary file. 91 | 92 | 93 | 94 | 95 | ### How can I implement the reader without using a mutable object? 96 | 97 | You do not need a mutable object, but you do need someway of keeping 98 | track of the current position in the token list. One way to implement 99 | this is to pass both the token list and the current position to the 100 | reader functions (read_form, read_list, read_atom, etc) and return 101 | both the parsed AST and the new token list position. If your language 102 | does not allow multiple values to be returned from functions then you 103 | may need to define a data structure to return both the new position 104 | and the parsed AST together. In other words, the pseudo-code would 105 | look something like this: 106 | 107 | ``` 108 | ast, position = read_list(tokens, position) 109 | ``` 110 | 111 | --- 112 | 113 | Answers for the following questions are TBD. 114 | 115 | ### How do I implement slurp in a language without the ability to read raw file data? 116 | 117 | 118 | 119 | ### How do I support raising/throwing arbitrary objects in a language that does not support that? 120 | ### What do I do if my implementation language only supports string exceptions? 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /mal/stepA_mal.mal: -------------------------------------------------------------------------------- 1 | (load-file "../mal/env.mal") 2 | (load-file "../mal/core.mal") 3 | 4 | ;; read 5 | (def! READ (fn* [strng] 6 | (read-string strng))) 7 | 8 | 9 | ;; eval 10 | (def! is-pair (fn* [x] 11 | (if (sequential? x) 12 | (if (> (count x) 0) 13 | true)))) 14 | 15 | (def! QUASIQUOTE (fn* [ast] 16 | (cond 17 | (not (is-pair ast)) 18 | (list 'quote ast) 19 | 20 | (= 'unquote (first ast)) 21 | (nth ast 1) 22 | 23 | (if (is-pair (first ast)) 24 | (if (= 'splice-unquote (first (first ast))) 25 | true)) 26 | (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) 27 | 28 | "else" 29 | (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) 30 | 31 | (def! is-macro-call (fn* [ast env] 32 | (if (list? ast) 33 | (let* [a0 (first ast)] 34 | (if (symbol? a0) 35 | (if (env-find env a0) 36 | (let* [m (meta (env-get env a0))] 37 | (if m 38 | (if (get m "ismacro") 39 | true))))))))) 40 | 41 | (def! MACROEXPAND (fn* [ast env] 42 | (if (is-macro-call ast env) 43 | (let* [mac (env-get env (first ast))] 44 | (MACROEXPAND (apply mac (rest ast)) env)) 45 | ast))) 46 | 47 | (def! eval-ast (fn* [ast env] (do 48 | ;;(do (prn "eval-ast" ast "/" (keys env)) ) 49 | (cond 50 | (symbol? ast) (env-get env ast) 51 | 52 | (list? ast) (map (fn* [exp] (EVAL exp env)) ast) 53 | 54 | (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) 55 | 56 | (map? ast) (apply hash-map 57 | (apply concat 58 | (map (fn* [k] [k (EVAL (get ast k) env)]) 59 | (keys ast)))) 60 | 61 | "else" ast)))) 62 | 63 | (def! LET (fn* [env args] 64 | (if (> (count args) 0) 65 | (do 66 | (env-set env (nth args 0) (EVAL (nth args 1) env)) 67 | (LET env (rest (rest args))))))) 68 | 69 | (def! EVAL (fn* [ast env] (do 70 | ;;(do (prn "EVAL" ast "/" (keys @env)) ) 71 | (if (not (list? ast)) 72 | (eval-ast ast env) 73 | 74 | ;; apply list 75 | (let* [ast (MACROEXPAND ast env)] 76 | (if (not (list? ast)) 77 | (eval-ast ast env) 78 | 79 | (let* [a0 (first ast)] 80 | (cond 81 | (nil? a0) 82 | ast 83 | 84 | (= 'def! a0) 85 | (env-set env (nth ast 1) (EVAL (nth ast 2) env)) 86 | 87 | (= 'let* a0) 88 | (let* [let-env (new-env env)] 89 | (do 90 | (LET let-env (nth ast 1)) 91 | (EVAL (nth ast 2) let-env))) 92 | 93 | (= 'quote a0) 94 | (nth ast 1) 95 | 96 | (= 'quasiquote a0) 97 | (let* [a1 (nth ast 1)] 98 | (EVAL (QUASIQUOTE a1) env)) 99 | 100 | (= 'defmacro! a0) 101 | (let* [a1 (nth ast 1) 102 | a2 (nth ast 2) 103 | f (EVAL a2 env) 104 | m (or (meta f) {}) 105 | mac (with-meta f (assoc m "ismacro" true))] 106 | (env-set env a1 mac)) 107 | 108 | (= 'macroexpand a0) 109 | (let* [a1 (nth ast 1)] 110 | (MACROEXPAND a1 env)) 111 | 112 | (= 'try* a0) 113 | (if (= 'catch* (nth (nth ast 2) 0)) 114 | (try* 115 | (EVAL (nth ast 1) env) 116 | (catch* exc 117 | (EVAL (nth (nth ast 2) 2) 118 | (new-env env 119 | [(nth (nth ast 2)1)] 120 | [exc])))) 121 | (EVAL (nth ast 1) env)) 122 | 123 | (= 'do a0) 124 | (let* [el (eval-ast (rest ast) env)] 125 | (nth el (- (count el) 1))) 126 | 127 | (= 'if a0) 128 | (let* [cond (EVAL (nth ast 1) env)] 129 | (if (or (= cond nil) (= cond false)) 130 | (if (> (count ast) 3) 131 | (EVAL (nth ast 3) env) 132 | nil) 133 | (EVAL (nth ast 2) env))) 134 | 135 | (= 'fn* a0) 136 | (fn* [& args] 137 | (EVAL (nth ast 2) (new-env env (nth ast 1) args))) 138 | 139 | "else" 140 | (let* [el (eval-ast ast env) 141 | f (first el) 142 | args (rest el)] 143 | (apply f args)))))))))) 144 | 145 | 146 | ;; print 147 | (def! PRINT (fn* [exp] (pr-str exp))) 148 | 149 | ;; repl 150 | (def! repl-env (new-env)) 151 | (def! rep (fn* [strng] 152 | (PRINT (EVAL (READ strng) repl-env)))) 153 | 154 | ;; core.mal: defined directly using mal 155 | (map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns) 156 | (env-set repl-env 'eval (fn* [ast] (EVAL ast repl-env))) 157 | (env-set repl-env '*ARGV* (rest *ARGV*)) 158 | 159 | ;; core.mal: defined using the new language itself 160 | (rep (str "(def! *host-language* \"" *host-language* "-mal\")")) 161 | (rep "(def! not (fn* [a] (if a false true)))") 162 | (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") 163 | (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)))))))") 164 | (rep "(def! *gensym-counter* (atom 0))") 165 | (rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") 166 | (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") 167 | 168 | ;; repl loop 169 | (def! repl-loop (fn* [] 170 | (let* [line (readline "mal-user> ")] 171 | (if line 172 | (do 173 | (if (not (= "" line)) 174 | (try* 175 | (println (rep line)) 176 | (catch* exc 177 | (println "Uncaught exception:" exc)))) 178 | (repl-loop)))))) 179 | 180 | (def! -main (fn* [& args] 181 | (if (> (count args) 0) 182 | (rep (str "(load-file \"" (first args) "\")")) 183 | (do 184 | (rep "(println (str \"Mal [\" *host-language* \"]\"))") 185 | (repl-loop))))) 186 | (apply -main *ARGV*) 187 | -------------------------------------------------------------------------------- /rust/src/bin/step3_env.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::core; 4 | use mal_rust::env::Env; 5 | use mal_rust::printer::pr_str; 6 | use mal_rust::reader::read_str; 7 | use mal_rust::readline::Readline; 8 | use mal_rust::types::*; 9 | 10 | use std::collections::BTreeMap; 11 | 12 | fn main() { 13 | let mut readline = Readline::new("user> "); 14 | let mut repl_env = Env::new(None); 15 | repl_env.set( 16 | "+", 17 | MalType::function(Function { 18 | func: Box::new(core::add), 19 | env: None, 20 | }), 21 | ); 22 | repl_env.set( 23 | "-", 24 | MalType::function(Function { 25 | func: Box::new(core::subtract), 26 | env: None, 27 | }), 28 | ); 29 | repl_env.set( 30 | "*", 31 | MalType::function(Function { 32 | func: Box::new(core::multiply), 33 | env: None, 34 | }), 35 | ); 36 | repl_env.set( 37 | "/", 38 | MalType::function(Function { 39 | func: Box::new(core::divide), 40 | env: None, 41 | }), 42 | ); 43 | loop { 44 | match readline.get() { 45 | Some(line) => { 46 | if line.len() > 0 { 47 | let result = rep(line, &mut repl_env); 48 | match result { 49 | Ok(str) => println!("{}", str), 50 | Err(MalError::BlankLine) => {} 51 | Err(err) => println!("{}", err), 52 | } 53 | } 54 | } 55 | None => break, 56 | } 57 | } 58 | readline.save_history(); 59 | } 60 | 61 | fn rep(input: String, repl_env: &Env) -> Result { 62 | let out = read(input)?; 63 | let out = eval(out, repl_env)?; 64 | let out = print(out); 65 | Ok(out) 66 | } 67 | 68 | fn read(code: String) -> MalResult { 69 | read_str(&code) 70 | } 71 | 72 | fn eval(mut ast: MalType, repl_env: &Env) -> MalResult { 73 | if ast.is_list() { 74 | if list_len(&ast) == 0 { 75 | Ok(ast) 76 | } else if is_special_form(&ast) { 77 | process_special_form(&mut ast, repl_env) 78 | } else { 79 | let new_ast = eval_ast(ast, repl_env)?; 80 | if let Some(vec) = new_ast.list_val() { 81 | if vec.len() > 0 { 82 | let mut vec = vec.clone(); 83 | let first = vec.remove(0); 84 | if let Some(Function { func, .. }) = first.function_val() { 85 | func(&mut vec, None) 86 | } else { 87 | Err(MalError::NotAFunction(first.clone())) 88 | } 89 | } else { 90 | panic!("Eval'd list is empty!") 91 | } 92 | } else { 93 | panic!("Eval'd list is no longer a list!") 94 | } 95 | } 96 | } else { 97 | Ok(eval_ast(ast, repl_env)?) 98 | } 99 | } 100 | 101 | fn print(ast: MalType) -> String { 102 | pr_str(&ast, true) 103 | } 104 | 105 | fn eval_ast(ast: MalType, repl_env: &Env) -> MalResult { 106 | if let Some(symbol) = ast.symbol_val() { 107 | if let Ok(val) = repl_env.get(&symbol) { 108 | return Ok(val.clone()); 109 | } else { 110 | return Err(MalError::SymbolUndefined(symbol.to_string())); 111 | } 112 | } else if let Some(vec) = ast.list_or_vector_val() { 113 | let results: Result, MalError> = vec.into_iter() 114 | .map(|item| eval(item.clone(), repl_env)) 115 | .collect(); 116 | if ast.is_list() { 117 | return Ok(MalType::list(results?)); 118 | } else { 119 | return Ok(MalType::vector(results?)); 120 | } 121 | } else if let Some(map) = ast.hashmap_val() { 122 | let mut new_map = BTreeMap::new(); 123 | for (key, val) in map { 124 | new_map.insert(key.clone(), eval(val.clone(), repl_env)?); 125 | } 126 | let mut map = MalType::hashmap(new_map); 127 | return Ok(map); 128 | }; 129 | Ok(ast) 130 | } 131 | 132 | fn list_len(list: &MalType) -> usize { 133 | if let Some(vec) = list.list_or_vector_val() { 134 | vec.len() 135 | } else { 136 | panic!("Expected a list but got: {:?}", list) 137 | } 138 | } 139 | 140 | fn is_special_form(ast: &MalType) -> bool { 141 | if let Some(vec) = ast.list_val() { 142 | if let Some(sym) = vec[0].symbol_val() { 143 | match sym { 144 | "def!" | "let*" => return true, 145 | _ => {} 146 | } 147 | } 148 | } 149 | false 150 | } 151 | 152 | fn process_special_form(ast: &mut MalType, repl_env: &Env) -> MalResult { 153 | if let Some(vec) = ast.list_val() { 154 | let mut vec = vec.clone(); 155 | if let Some(special) = vec.remove(0).symbol_val() { 156 | return match special { 157 | "def!" => special_def(&mut vec, repl_env), 158 | "let*" => special_let(&mut vec, repl_env), 159 | _ => panic!(format!("Unhandled special form: {}", special)), 160 | }; 161 | } 162 | } 163 | panic!("Expected a List for a special form!") 164 | } 165 | 166 | fn special_def(vec: &mut Vec, repl_env: &Env) -> MalResult { 167 | let name = vec.remove(0); 168 | if let Some(sym) = name.symbol_val() { 169 | let val = eval(vec.remove(0), repl_env)?; 170 | repl_env.set(sym, val.clone()); 171 | Ok(val) 172 | } else { 173 | Err(MalError::WrongArguments(format!( 174 | "Expected a symbol as the first argument to def! but got: {:?}", 175 | name 176 | ))) 177 | } 178 | } 179 | 180 | fn special_let(vec: &mut Vec, repl_env: &Env) -> MalResult { 181 | let inner_repl_env = Env::new(Some(&repl_env)); 182 | let bindings = vec.remove(0); 183 | if let Some(bindings) = bindings.list_or_vector_val() { 184 | if bindings.len() % 2 != 0 { 185 | return Err(MalError::Parse( 186 | "Odd number of let* binding values!".to_string(), 187 | )); 188 | } 189 | let mut bindings = bindings.clone(); 190 | loop { 191 | if bindings.len() == 0 { 192 | break; 193 | } 194 | if let Some(name) = bindings.remove(0).symbol_val() { 195 | let val = eval(bindings.remove(0), &inner_repl_env)?; 196 | inner_repl_env.set(name, val); 197 | } else { 198 | return Err(MalError::Parse("Expected symbol".to_string())); 199 | } 200 | } 201 | let rest = vec.remove(0); 202 | eval(rest, &inner_repl_env) 203 | } else { 204 | Err(MalError::WrongArguments(format!( 205 | "Expected a vector or list as the first argument to let* but got: {:?}", 206 | bindings 207 | ))) 208 | } 209 | } 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | use super::*; 214 | 215 | #[test] 216 | fn test_def() { 217 | let mut repl_env = Env::new(None); 218 | rep("(def! x 1)".to_string(), &mut repl_env).unwrap(); 219 | let result = rep("x".to_string(), &mut repl_env).unwrap(); 220 | assert_eq!("1", format!("{}", result)); 221 | } 222 | 223 | #[test] 224 | fn test_let() { 225 | let mut repl_env = Env::new(None); 226 | let result = rep("(let* [x 1 y 2 z x] [x y z])".to_string(), &mut repl_env).unwrap(); 227 | assert_eq!("[1 2 1]", format!("{}", result)); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/step9_try.mal: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; Testing try*/catch* 3 | 4 | (try* 123 (catch* e 456)) 5 | ;=>123 6 | 7 | (try* (abc 1 2) (catch* exc (prn "exc is:" exc))) 8 | ; "exc is:" "'abc' not found" 9 | ;=>nil 10 | 11 | (try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7))) 12 | ; "exc:" "my exception" 13 | ;=>7 14 | 15 | ;;; Test that throw is a function: 16 | (try* (map throw (list "my err")) (catch* exc exc)) 17 | ;=>"my err" 18 | 19 | 20 | ;; 21 | ;; Testing builtin functions 22 | 23 | (symbol? 'abc) 24 | ;=>true 25 | (symbol? "abc") 26 | ;=>false 27 | 28 | (nil? nil) 29 | ;=>true 30 | (nil? true) 31 | ;=>false 32 | 33 | (true? true) 34 | ;=>true 35 | (true? false) 36 | ;=>false 37 | (true? true?) 38 | ;=>false 39 | 40 | (false? false) 41 | ;=>true 42 | (false? true) 43 | ;=>false 44 | 45 | ;; Testing apply function with core functions 46 | (apply + (list 2 3)) 47 | ;=>5 48 | (apply + 4 (list 5)) 49 | ;=>9 50 | (apply prn (list 1 2 "3" (list))) 51 | ; 1 2 "3" () 52 | ;=>nil 53 | (apply prn 1 2 (list "3" (list))) 54 | ; 1 2 "3" () 55 | ;=>nil 56 | (apply list (list)) 57 | ;=>() 58 | (apply symbol? (list (quote two))) 59 | ;=>true 60 | 61 | ;; Testing apply function with user functions 62 | (apply (fn* (a b) (+ a b)) (list 2 3)) 63 | ;=>5 64 | (apply (fn* (a b) (+ a b)) 4 (list 5)) 65 | ;=>9 66 | 67 | ;; Testing map function 68 | (def! nums (list 1 2 3)) 69 | (def! double (fn* (a) (* 2 a))) 70 | (double 3) 71 | ;=>6 72 | (map double nums) 73 | ;=>(2 4 6) 74 | (map (fn* (x) (symbol? x)) (list 1 (quote two) "three")) 75 | ;=>(false true false) 76 | 77 | ;>>> deferrable=True 78 | ;; 79 | ;; ------- Deferrable Functionality ---------- 80 | ;; ------- (Needed for self-hosting) ------- 81 | 82 | ;; Testing symbol and keyword functions 83 | (symbol? :abc) 84 | ;=>false 85 | (symbol? 'abc) 86 | ;=>true 87 | (symbol? "abc") 88 | ;=>false 89 | (symbol? (symbol "abc")) 90 | ;=>true 91 | (keyword? :abc) 92 | ;=>true 93 | (keyword? 'abc) 94 | ;=>false 95 | (keyword? "abc") 96 | ;=>false 97 | (keyword? "") 98 | ;=>false 99 | (keyword? (keyword "abc")) 100 | ;=>true 101 | 102 | (symbol "abc") 103 | ;=>abc 104 | ;;;TODO: all implementations should suppport this too 105 | ;;;(keyword :abc) 106 | ;;;;=>:abc 107 | (keyword "abc") 108 | ;=>:abc 109 | 110 | ;; Testing sequential? function 111 | 112 | (sequential? (list 1 2 3)) 113 | ;=>true 114 | (sequential? [15]) 115 | ;=>true 116 | (sequential? sequential?) 117 | ;=>false 118 | (sequential? nil) 119 | ;=>false 120 | (sequential? "abc") 121 | ;=>false 122 | 123 | ;; Testing apply function with core functions and arguments in vector 124 | (apply + 4 [5]) 125 | ;=>9 126 | (apply prn 1 2 ["3" 4]) 127 | ; 1 2 "3" 4 128 | ;=>nil 129 | (apply list []) 130 | ;=>() 131 | ;; Testing apply function with user functions and arguments in vector 132 | (apply (fn* (a b) (+ a b)) [2 3]) 133 | ;=>5 134 | (apply (fn* (a b) (+ a b)) 4 [5]) 135 | ;=>9 136 | 137 | 138 | ;; Testing map function with vectors 139 | (map (fn* (a) (* 2 a)) [1 2 3]) 140 | ;=>(2 4 6) 141 | 142 | (map (fn* [& args] (list? args)) [1 2]) 143 | ;=>(true true) 144 | 145 | ;; Testing vector functions 146 | 147 | (vector? [10 11]) 148 | ;=>true 149 | (vector? '(12 13)) 150 | ;=>false 151 | (vector 3 4 5) 152 | ;=>[3 4 5] 153 | 154 | (map? {}) 155 | ;=>true 156 | (map? '()) 157 | ;=>false 158 | (map? []) 159 | ;=>false 160 | (map? 'abc) 161 | ;=>false 162 | (map? :abc) 163 | ;=>false 164 | 165 | ;; 166 | ;; Testing hash-maps 167 | (hash-map "a" 1) 168 | ;=>{"a" 1} 169 | 170 | {"a" 1} 171 | ;=>{"a" 1} 172 | 173 | (assoc {} "a" 1) 174 | ;=>{"a" 1} 175 | 176 | (get (assoc (assoc {"a" 1 } "b" 2) "c" 3) "a") 177 | ;=>1 178 | 179 | (def! hm1 (hash-map)) 180 | ;=>{} 181 | 182 | (map? hm1) 183 | ;=>true 184 | (map? 1) 185 | ;=>false 186 | (map? "abc") 187 | ;=>false 188 | 189 | (get nil "a") 190 | ;=>nil 191 | 192 | (get hm1 "a") 193 | ;=>nil 194 | 195 | (contains? hm1 "a") 196 | ;=>false 197 | 198 | (def! hm2 (assoc hm1 "a" 1)) 199 | ;=>{"a" 1} 200 | 201 | (get hm1 "a") 202 | ;=>nil 203 | 204 | (contains? hm1 "a") 205 | ;=>false 206 | 207 | (get hm2 "a") 208 | ;=>1 209 | 210 | (contains? hm2 "a") 211 | ;=>true 212 | 213 | 214 | ;;; TODO: fix. Clojure returns nil but this breaks mal impl 215 | (keys hm1) 216 | ;=>() 217 | 218 | (keys hm2) 219 | ;=>("a") 220 | 221 | ;;; TODO: fix. Clojure returns nil but this breaks mal impl 222 | (vals hm1) 223 | ;=>() 224 | 225 | (vals hm2) 226 | ;=>(1) 227 | 228 | (count (keys (assoc hm2 "b" 2 "c" 3))) 229 | ;=>3 230 | 231 | ;; Testing keywords as hash-map keys 232 | (get {:abc 123} :abc) 233 | ;=>123 234 | (contains? {:abc 123} :abc) 235 | ;=>true 236 | (contains? {:abcd 123} :abc) 237 | ;=>false 238 | (assoc {} :bcd 234) 239 | ;=>{:bcd 234} 240 | (keyword? (nth (keys {:abc 123 :def 456}) 0)) 241 | ;=>true 242 | ;;; TODO: support : in strings in make impl 243 | ;;;(keyword? (nth (keys {":abc" 123 ":def" 456}) 0)) 244 | ;;;;=>false 245 | (keyword? (nth (vals {"a" :abc "b" :def}) 0)) 246 | ;=>true 247 | 248 | ;; Testing whether assoc updates properly 249 | (def! hm4 (assoc {:a 1 :b 2} :a 3 :c 1)) 250 | (get hm4 :a) 251 | ;=>3 252 | (get hm4 :b) 253 | ;=>2 254 | (get hm4 :c) 255 | ;=>1 256 | 257 | ;; Testing nil as hash-map values 258 | (contains? {:abc nil} :abc) 259 | ;=>true 260 | (assoc {} :bcd nil) 261 | ;=>{:bcd nil} 262 | 263 | ;; 264 | ;; Additional str and pr-str tests 265 | 266 | (str "A" {:abc "val"} "Z") 267 | ;=>"A{:abc val}Z" 268 | 269 | (str true "." false "." nil "." :keyw "." 'symb) 270 | ;=>"true.false.nil.:keyw.symb" 271 | 272 | (pr-str "A" {:abc "val"} "Z") 273 | ;=>"\"A\" {:abc \"val\"} \"Z\"" 274 | 275 | (pr-str true "." false "." nil "." :keyw "." 'symb) 276 | ;=>"true \".\" false \".\" nil \".\" :keyw \".\" symb" 277 | 278 | (def! s (str {:abc "val1" :def "val2"})) 279 | (or (= s "{:abc val1 :def val2}") (= s "{:def val2 :abc val1}")) 280 | ;=>true 281 | 282 | (def! p (pr-str {:abc "val1" :def "val2"})) 283 | (or (= p "{:abc \"val1\" :def \"val2\"}") (= p "{:def \"val2\" :abc \"val1\"}")) 284 | ;=>true 285 | 286 | ;; 287 | ;; Test extra function arguments as Mal List (bypassing TCO with apply) 288 | (apply (fn* (& more) (list? more)) [1 2 3]) 289 | ;=>true 290 | (apply (fn* (& more) (list? more)) []) 291 | ;=>true 292 | (apply (fn* (a & more) (list? more)) [1]) 293 | ;=>true 294 | 295 | ;>>> soft=True 296 | ;>>> optional=True 297 | ;; 298 | ;; ------- Optional Functionality -------------- 299 | ;; ------- (Not needed for self-hosting) ------- 300 | 301 | 302 | ;;;TODO: fix so long lines don't trigger ANSI escape codes ;;;(try* 303 | ;;;(try* (throw ["data" "foo"]) (catch* exc (do (prn "exc is:" exc) 7))) ;;;; 304 | ;;;; "exc is:" ["data" "foo"] ;;;;=>7 305 | ;;;;=>7 306 | 307 | ;; 308 | ;; Testing throwing non-strings 309 | (try* (throw (list 1 2 3)) (catch* exc (do (prn "err:" exc) 7))) 310 | ; "err:" (1 2 3) 311 | ;=>7 312 | 313 | ;; 314 | ;; Testing dissoc 315 | (def! hm3 (assoc hm2 "b" 2)) 316 | (count (keys hm3)) 317 | ;=>2 318 | (count (vals hm3)) 319 | ;=>2 320 | (dissoc hm3 "a") 321 | ;=>{"b" 2} 322 | (dissoc hm3 "a" "b") 323 | ;=>{} 324 | (dissoc hm3 "a" "b" "c") 325 | ;=>{} 326 | (count (keys hm3)) 327 | ;=>2 328 | 329 | (dissoc {:cde 345 :fgh 456} :cde) 330 | ;=>{:fgh 456} 331 | (dissoc {:cde nil :fgh 456} :cde) 332 | ;=>{:fgh 456} 333 | 334 | ;; 335 | ;; Testing equality of hash-maps 336 | (= {} {}) 337 | ;=>true 338 | (= {:a 11 :b 22} (hash-map :b 22 :a 11)) 339 | ;=>true 340 | (= {:a 11 :b [22 33]} (hash-map :b [22 33] :a 11)) 341 | ;=>true 342 | (= {:a 11 :b {:c 33}} (hash-map :b {:c 33} :a 11)) 343 | ;=>true 344 | (= {:a 11 :b 22} (hash-map :b 23 :a 11)) 345 | ;=>false 346 | (= {:a 11 :b 22} (hash-map :a 11)) 347 | ;=>false 348 | (= {:a [11 22]} {:a (list 11 22)}) 349 | ;=>true 350 | (= {:a 11 :b 22} (list :a 11 :b 22)) 351 | ;=>false 352 | (= {} []) 353 | ;=>false 354 | (= [] {}) 355 | ;=>false 356 | 357 | -------------------------------------------------------------------------------- /tests/step4_if_fn_do.mal: -------------------------------------------------------------------------------- 1 | ;; ----------------------------------------------------- 2 | 3 | 4 | ;; Testing list functions 5 | (list) 6 | ;=>() 7 | (list? (list)) 8 | ;=>true 9 | (empty? (list)) 10 | ;=>true 11 | (empty? (list 1)) 12 | ;=>false 13 | (list 1 2 3) 14 | ;=>(1 2 3) 15 | (count (list 1 2 3)) 16 | ;=>3 17 | (count (list)) 18 | ;=>0 19 | (count nil) 20 | ;=>0 21 | (if (> (count (list 1 2 3)) 3) "yes" "no") 22 | ;=>"no" 23 | (if (>= (count (list 1 2 3)) 3) "yes" "no") 24 | ;=>"yes" 25 | 26 | 27 | ;; Testing if form 28 | (if true 7 8) 29 | ;=>7 30 | (if false 7 8) 31 | ;=>8 32 | (if true (+ 1 7) (+ 1 8)) 33 | ;=>8 34 | (if false (+ 1 7) (+ 1 8)) 35 | ;=>9 36 | (if nil 7 8) 37 | ;=>8 38 | (if 0 7 8) 39 | ;=>7 40 | (if "" 7 8) 41 | ;=>7 42 | (if (list) 7 8) 43 | ;=>7 44 | (if (list 1 2 3) 7 8) 45 | ;=>7 46 | (= (list) nil) 47 | ;=>false 48 | 49 | 50 | ;; Testing 1-way if form 51 | (if false (+ 1 7)) 52 | ;=>nil 53 | (if nil 8 7) 54 | ;=>7 55 | (if true (+ 1 7)) 56 | ;=>8 57 | 58 | 59 | ;; Testing basic conditionals 60 | (= 2 1) 61 | ;=>false 62 | (= 1 1) 63 | ;=>true 64 | (= 1 2) 65 | ;=>false 66 | (= 1 (+ 1 1)) 67 | ;=>false 68 | (= 2 (+ 1 1)) 69 | ;=>true 70 | (= nil 1) 71 | ;=>false 72 | (= nil nil) 73 | ;=>true 74 | 75 | (> 2 1) 76 | ;=>true 77 | (> 1 1) 78 | ;=>false 79 | (> 1 2) 80 | ;=>false 81 | 82 | (>= 2 1) 83 | ;=>true 84 | (>= 1 1) 85 | ;=>true 86 | (>= 1 2) 87 | ;=>false 88 | 89 | (< 2 1) 90 | ;=>false 91 | (< 1 1) 92 | ;=>false 93 | (< 1 2) 94 | ;=>true 95 | 96 | (<= 2 1) 97 | ;=>false 98 | (<= 1 1) 99 | ;=>true 100 | (<= 1 2) 101 | ;=>true 102 | 103 | 104 | ;; Testing equality 105 | (= 1 1) 106 | ;=>true 107 | (= 0 0) 108 | ;=>true 109 | (= 1 0) 110 | ;=>false 111 | (= "" "") 112 | ;=>true 113 | (= "abc" "abc") 114 | ;=>true 115 | (= "abc" "") 116 | ;=>false 117 | (= "" "abc") 118 | ;=>false 119 | (= "abc" "def") 120 | ;=>false 121 | (= "abc" "ABC") 122 | ;=>false 123 | (= true true) 124 | ;=>true 125 | (= false false) 126 | ;=>true 127 | (= nil nil) 128 | ;=>true 129 | 130 | (= (list) (list)) 131 | ;=>true 132 | (= (list 1 2) (list 1 2)) 133 | ;=>true 134 | (= (list 1) (list)) 135 | ;=>false 136 | (= (list) (list 1)) 137 | ;=>false 138 | (= 0 (list)) 139 | ;=>false 140 | (= (list) 0) 141 | ;=>false 142 | (= (list) "") 143 | ;=>false 144 | (= "" (list)) 145 | ;=>false 146 | 147 | 148 | ;; Testing builtin and user defined functions 149 | (+ 1 2) 150 | ;=>3 151 | ( (fn* (a b) (+ b a)) 3 4) 152 | ;=>7 153 | ( (fn* () 4) ) 154 | ;=>4 155 | 156 | ( (fn* (f x) (f x)) (fn* (a) (+ 1 a)) 7) 157 | ;=>8 158 | 159 | 160 | ;; Testing closures 161 | ( ( (fn* (a) (fn* (b) (+ a b))) 5) 7) 162 | ;=>12 163 | 164 | (def! gen-plus5 (fn* () (fn* (b) (+ 5 b)))) 165 | (def! plus5 (gen-plus5)) 166 | (plus5 7) 167 | ;=>12 168 | 169 | (def! gen-plusX (fn* (x) (fn* (b) (+ x b)))) 170 | (def! plus7 (gen-plusX 7)) 171 | (plus7 8) 172 | ;=>15 173 | 174 | ;; Testing do form 175 | (do (prn "prn output1")) 176 | ; "prn output1" 177 | ;=>nil 178 | (do (prn "prn output2") 7) 179 | ; "prn output2" 180 | ;=>7 181 | (do (prn "prn output1") (prn "prn output2") (+ 1 2)) 182 | ; "prn output1" 183 | ; "prn output2" 184 | ;=>3 185 | 186 | (do (def! a 6) 7 (+ a 8)) 187 | ;=>14 188 | a 189 | ;=>6 190 | 191 | ;; Testing special form case-sensitivity 192 | (def! DO (fn* (a) 7)) 193 | (DO 3) 194 | ;=>7 195 | 196 | ;; Testing recursive sumdown function 197 | (def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) 198 | (sumdown 1) 199 | ;=>1 200 | (sumdown 2) 201 | ;=>3 202 | (sumdown 6) 203 | ;=>21 204 | 205 | 206 | ;; Testing recursive fibonacci function 207 | (def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2))))))) 208 | (fib 1) 209 | ;=>1 210 | (fib 2) 211 | ;=>2 212 | (fib 4) 213 | ;=>5 214 | ;;; Too slow for bash, erlang, make and miniMAL 215 | ;;;(fib 10) 216 | ;;;;=>89 217 | 218 | 219 | ;>>> deferrable=True 220 | ;; 221 | ;; -------- Deferrable Functionality -------- 222 | 223 | ;; Testing variable length arguments 224 | 225 | ( (fn* (& more) (count more)) 1 2 3) 226 | ;=>3 227 | ( (fn* (& more) (list? more)) 1 2 3) 228 | ;=>true 229 | ( (fn* (& more) (count more)) 1) 230 | ;=>1 231 | ( (fn* (& more) (count more)) ) 232 | ;=>0 233 | ( (fn* (& more) (list? more)) ) 234 | ;=>true 235 | ( (fn* (a & more) (count more)) 1 2 3) 236 | ;=>2 237 | ( (fn* (a & more) (count more)) 1) 238 | ;=>0 239 | ( (fn* (a & more) (list? more)) 1) 240 | ;=>true 241 | 242 | 243 | ;; Testing language defined not function 244 | (not false) 245 | ;=>true 246 | (not true) 247 | ;=>false 248 | (not "a") 249 | ;=>false 250 | (not 0) 251 | ;=>false 252 | 253 | 254 | ;; ----------------------------------------------------- 255 | 256 | ;; Testing string quoting 257 | 258 | "" 259 | ;=>"" 260 | 261 | "abc" 262 | ;=>"abc" 263 | 264 | "abc def" 265 | ;=>"abc def" 266 | 267 | "\"" 268 | ;=>"\"" 269 | 270 | "abc\ndef\nghi" 271 | ;=>"abc\ndef\nghi" 272 | 273 | "abc\\def\\ghi" 274 | ;=>"abc\\def\\ghi" 275 | 276 | "\\n" 277 | ;=>"\\n" 278 | 279 | ;; Testing pr-str 280 | 281 | (pr-str) 282 | ;=>"" 283 | 284 | (pr-str "") 285 | ;=>"\"\"" 286 | 287 | (pr-str "abc") 288 | ;=>"\"abc\"" 289 | 290 | (pr-str "abc def" "ghi jkl") 291 | ;=>"\"abc def\" \"ghi jkl\"" 292 | 293 | (pr-str "\"") 294 | ;=>"\"\\\"\"" 295 | 296 | (pr-str (list 1 2 "abc" "\"") "def") 297 | ;=>"(1 2 \"abc\" \"\\\"\") \"def\"" 298 | 299 | (pr-str "abc\ndef\nghi") 300 | ;=>"\"abc\\ndef\\nghi\"" 301 | 302 | (pr-str "abc\\def\\ghi") 303 | ;=>"\"abc\\\\def\\\\ghi\"" 304 | 305 | (pr-str (list)) 306 | ;=>"()" 307 | 308 | ;; Testing str 309 | 310 | (str) 311 | ;=>"" 312 | 313 | (str "") 314 | ;=>"" 315 | 316 | (str "abc") 317 | ;=>"abc" 318 | 319 | (str "\"") 320 | ;=>"\"" 321 | 322 | (str 1 "abc" 3) 323 | ;=>"1abc3" 324 | 325 | (str "abc def" "ghi jkl") 326 | ;=>"abc defghi jkl" 327 | 328 | (str "abc\ndef\nghi") 329 | ;=>"abc\ndef\nghi" 330 | 331 | (str "abc\\def\\ghi") 332 | ;=>"abc\\def\\ghi" 333 | 334 | (str (list 1 2 "abc" "\"") "def") 335 | ;=>"(1 2 abc \")def" 336 | 337 | (str (list)) 338 | ;=>"()" 339 | 340 | ;; Testing prn 341 | (prn) 342 | ; 343 | ;=>nil 344 | 345 | (prn "") 346 | ; "" 347 | ;=>nil 348 | 349 | (prn "abc") 350 | ; "abc" 351 | ;=>nil 352 | 353 | (prn "abc def" "ghi jkl") 354 | ; "abc def" "ghi jkl" 355 | 356 | (prn "\"") 357 | ; "\"" 358 | ;=>nil 359 | 360 | (prn "abc\ndef\nghi") 361 | ; "abc\ndef\nghi" 362 | ;=>nil 363 | 364 | (prn "abc\\def\\ghi") 365 | ; "abc\\def\\ghi" 366 | nil 367 | 368 | (prn (list 1 2 "abc" "\"") "def") 369 | ; (1 2 "abc" "\"") "def" 370 | ;=>nil 371 | 372 | 373 | ;; Testing println 374 | (println) 375 | ; 376 | ;=>nil 377 | 378 | (println "") 379 | ; 380 | ;=>nil 381 | 382 | (println "abc") 383 | ; abc 384 | ;=>nil 385 | 386 | (println "abc def" "ghi jkl") 387 | ; abc def ghi jkl 388 | 389 | (println "\"") 390 | ; " 391 | ;=>nil 392 | 393 | (println "abc\ndef\nghi") 394 | ; abc 395 | ; def 396 | ; ghi 397 | ;=>nil 398 | 399 | (println "abc\\def\\ghi") 400 | ; abc\def\ghi 401 | ;=>nil 402 | 403 | (println (list 1 2 "abc" "\"") "def") 404 | ; (1 2 abc ") def 405 | ;=>nil 406 | 407 | ;>>> optional=True 408 | ;; 409 | ;; -------- Optional Functionality -------- 410 | 411 | ;; Testing keywords 412 | (= :abc :abc) 413 | ;=>true 414 | (= :abc :def) 415 | ;=>false 416 | (= :abc ":abc") 417 | ;=>false 418 | 419 | ;; Testing vector truthiness 420 | (if [] 7 8) 421 | ;=>7 422 | 423 | ;; Testing vector printing 424 | (pr-str [1 2 "abc" "\""] "def") 425 | ;=>"[1 2 \"abc\" \"\\\"\"] \"def\"" 426 | 427 | (pr-str []) 428 | ;=>"[]" 429 | 430 | (str [1 2 "abc" "\""] "def") 431 | ;=>"[1 2 abc \"]def" 432 | 433 | (str []) 434 | ;=>"[]" 435 | 436 | 437 | ;; Testing vector functions 438 | (count [1 2 3]) 439 | ;=>3 440 | (empty? [1 2 3]) 441 | ;=>false 442 | (empty? []) 443 | ;=>true 444 | (list? [4 5 6]) 445 | ;=>false 446 | 447 | ;; Testing vector equality 448 | (= [] (list)) 449 | ;=>true 450 | (= [7 8] [7 8]) 451 | ;=>true 452 | (= (list 1 2) [1 2]) 453 | ;=>true 454 | (= (list 1) []) 455 | ;=>false 456 | (= [] [1]) 457 | ;=>false 458 | (= 0 []) 459 | ;=>false 460 | (= [] 0) 461 | ;=>false 462 | (= [] "") 463 | ;=>false 464 | (= "" []) 465 | ;=>false 466 | 467 | ;; Testing vector parameter lists 468 | ( (fn* [] 4) ) 469 | ;=>4 470 | ( (fn* [f x] (f x)) (fn* [a] (+ 1 a)) 7) 471 | ;=>8 472 | 473 | ;; Nested vector/list equality 474 | (= [(list)] (list [])) 475 | ;=>true 476 | (= [1 2 (list 3 4 [5 6])] (list 1 2 [3 4 (list 5 6)])) 477 | ;=>true 478 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # Mal/Make-a-Lisp FAQ 2 | 3 | 4 | 5 | ### Why did you create mal/make-a-lisp? 6 | ### OR Why the name "mal"? 7 | ### OR Why? 8 | ### OR Wat? 9 | 10 | In November of 2013, Alan Dipert gave a [lightning talk at 11 | Clojure/conj](https://www.youtube.com/watch?v=bmHTFo2Rf2w#t=28m55s) 12 | about [gherkin](https://github.com/alandipert/gherkin), a Lisp 13 | implemented in bash. His presentation led me to ask myself the question 14 | of whether a Lisp could be created using the GNU Make macro language. 15 | As you have probably guessed, the answer to that question is yes. 16 | 17 | Interestingly, the current pedagogical/educational purpose of mal 18 | happened due to a semantic naming accident (naming is such a fraught 19 | task in computer science). If I am remembering correctly, the name 20 | "mal" original meant "MAke Lisp". I do not remember precisely why 21 | I continued to create more implementations, apart from the fact that 22 | it was a fun challenge, but after the make implementation, many of the 23 | others were relatively easy. At some point during that process, 24 | I realized that the multiple implementations and incremental steps 25 | (which was originally just for my own clarity) was a useful learning 26 | tool and so the "mal" name became a double entendre for "Make, A Lisp" 27 | and "make-a-lisp" (and eventually just the latter given that the make 28 | implementation is now just a small part of the whole). 29 | 30 | 31 | 32 | 33 | ### Why is some code split into steps and some code not? 34 | 35 | The split between code that goes in steps and code that goes into other files 36 | is not completely arbitrary (a bit arbitrary, but not completely). My rule of 37 | thumb is something like this: if the code is specific and necessary for 38 | implementing a Lisp then it belongs in the step files. If the purpose of the 39 | code is for implementing new dynamic data-types/objects and the functions or 40 | methods that operate on those types, then it goes in separate files. 41 | 42 | If the target language has types and functions that resemble mal types, then 43 | those files tend to be very small or non-existent. Examples: 44 | 45 | * the mal implementation has no types, reader, printer files and 46 | has a trivial core file (just to hoist underlying functions) 47 | * the Clojure implementation has no types file and fairly trivial 48 | reader and printer files (just to modify the Clojure reader/writer 49 | slightly) and a fairly trivial core file 50 | * ruby types and the functions that operate on them are very "Lispy" 51 | so the Ruby types file and core file are very small. 52 | 53 | The env file is somewhat more arbitrary, however, it is 54 | a self-contained module that is implemented early and changes very 55 | little after that, so I decided to separate it. Also, for languages 56 | that have hierarchical maps/dictionaries (e.g. Javascript 57 | objects/prototype chain), you do not necessarily need an env file. 58 | 59 | Another way of summarizing this answer is that the step files 60 | represent the core of what makes something a Lisp, the rest of the 61 | modules are just language specific details (they may be the harder 62 | than the Lisp part, but that is due to the nature of the target 63 | language not because of Lisp functionality per se). 64 | 65 | 66 | 67 | 68 | ### Why are the mal/make-a-lisp steps structured the way they are? 69 | 70 | ### OR Why is X functionality in step Y instead of step Z? 71 | 72 | There is no single consistent rule that I have used to determine which 73 | functionality goes in which step and the arrangement has changed 74 | numerous times since the beginning of the project. There are several 75 | different goals that I try and balance in determining which 76 | functionality goes into which step: 77 | 78 | * **Optimize Lisp learning**: I want developers who are unfamiliar with 79 | Lisp to be able to use the project and guide to learn about Lisp 80 | without becoming overwhelmed. In many Lisp introductions, concepts 81 | like quoting and homoiconicity (i.e. a user exposed eval function) 82 | are introduced early. But these are fairly foreign to most other 83 | languages so they are introduced in later steps in mal. I also try 84 | to not to concentrate too many Lisp concepts in a single step. So 85 | many steps contain one or two Lisp concepts plus some core function 86 | additions that support those concepts. 87 | 88 | * **Optimize implementation language learning (equal-ish step 89 | sizing)**: I try to structure the steps so that the target 90 | implementation can be learned incrementally. This goal is the one 91 | that has caused me to refactor the steps the most. Different 92 | languages have different areas that they optimize and make simple 93 | for the developer. For example, in Java (prior to 8) and PostScript 94 | creating the equivalent of anonymous functions and function closures 95 | is painful. In other languages, function closures are trivial, but 96 | IO and error handling are tedious when you are first learning the 97 | language (I am looking at you Haskell). So this goal is really about 98 | trying to balance step size across multiple languages. 99 | 100 | * **Practical results early and continuous feedback**: it is 101 | a scientific fact that many small rewards are more motivating than 102 | a single large reward (citation intentionally omitted, get a small 103 | reward by googling it yourself). Each step in mal adds new 104 | functionality that can actually be exercised by the implementer and, 105 | just as importantly, easily tested. 106 | 107 | Also, the step structure of mal/make-a-lisp is not perfect. It never 108 | will be perfect, but there are some areas that could be improved. The 109 | most glaring problem is that step1 is on the heavy/large size because 110 | in most languages you have to implement a good portion of the 111 | reader/printer before you can begin using/testing the step. The 112 | compromise I have settled on for now is to put extra detail in the 113 | process guide for step1 and to be clear that many of the types are 114 | deferrable until later. But I am always open to suggestions. 115 | 116 | 117 | 118 | 119 | ### Will you add my new implementation? 120 | 121 | Absolutely! I want mal to have a idiomatic implementation in every 122 | programming language. 123 | 124 | Here are a few guidelines for getting your implementation accepted 125 | into the main repository: 126 | 127 | * Your implementation should follow the existing mal steps and 128 | structure: Lisp-centric code (eval, eval_ast, quasiquote, 129 | macroexpand) in the step files, other code in reader, printer, env, 130 | and core files. See [code layout rationale](#code_split) above. 131 | I encourage you to create implementations that take mal in new 132 | directions for your own learning and experimentation, but for it to 133 | be included in the main repository I ask that it follows the steps 134 | and structure. 135 | 136 | * Your implementation should stick as much as possible to the accepted 137 | idioms and conventions in that language. Try to create an 138 | implementation that will not make an expert in that language say 139 | "Woah, that's a strange way of doing things". And on that topic, 140 | I make no guarantees that the existing implementations are 141 | particularly idiomatic in their target languages (improvements are 142 | welcome). However, if it is clear to me that your implementation is 143 | not idiomatic in a given language then I will probably ask you to 144 | improve it first. 145 | 146 | * Your implementation needs to be complete enough to self-host. This 147 | means that all the mandatory tests should pass in both direct and 148 | self-hosted modes: 149 | ```bash 150 | make "test^[IMPL_NAME]" 151 | make MAL_IMPL=[IMPL_NAME] "test^mal" 152 | ``` 153 | You do not need to pass the final optional tests for stepA that are 154 | marked as optional and not needed for self-hosting (except for the 155 | `time-ms` function which is needed to run the micro-benchmark tests). 156 | 157 | * Create a `Dockerfile` in your directory that installs all the 158 | packages necessary to build and run your implementation. Refer to other 159 | implementations for examples of what the Dockerfile should contain. 160 | Build your docker image and tag it `kanaka/mal-test-[IMPL_NAME]`. 161 | The top-level Makefile has support for building/testing within 162 | docker with the `DOCKERIZE` flag: 163 | ```bash 164 | make DOCKERIZE=1 "test^[IMPL_NAME]" 165 | make DOCKERIZE=1 MAL_IMPL=[IMPL_NAME] "test^mal" 166 | ``` 167 | 168 | * Make sure the Travis build and test scripts pass locally: 169 | ```bash 170 | IMPL=[IMPL_NAME] ./.travis_build.sh 171 | ./.travis_test.sh test [IMPL_NAME] 172 | ``` 173 | 174 | * If you are creating a new implementation for an existing 175 | implementation (or somebody beats you to the punch while you are 176 | working on it), there is still a chance I will merge your 177 | implementation. If you can make a compelling argument that your 178 | implementation is more idiomatic or significantly better than the 179 | existing implementation then I may replace the existing one. 180 | However, if your approach is different or unique from the existing 181 | implementation, there is still a good chance I will merge your 182 | implementation side-by-side with the existing one. In that case 183 | I will add your github username as a suffix to the language 184 | implementation directory. At the very least, even if I decide not to 185 | merge your implementation, I am certainly willing to link to you 186 | implementation once it is completed. 187 | 188 | * You do not need to implement line editing (i.e. readline) 189 | functionality for your implementation, however, it is a nice 190 | convenience for users of your implementation and I personally find 191 | it saves a lot of time when I am creating a new implementation to 192 | have line edit support early in the process. 193 | 194 | --- 195 | 196 | **Good questions that either don't have answer or need more detail** 197 | 198 | ### Why do some mal forms end in "\*" or "!" (swap!, def!, let\*, etc)? 199 | -------------------------------------------------------------------------------- /rust/src/bin/step5_tco.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::env::Env; 4 | use mal_rust::printer::pr_str; 5 | use mal_rust::reader::read_str; 6 | use mal_rust::readline::Readline; 7 | use mal_rust::types::*; 8 | use mal_rust::core::NS; 9 | 10 | use std::collections::BTreeMap; 11 | 12 | fn main() { 13 | let mut readline = Readline::new("user> "); 14 | let repl_env = top_repl_env(); 15 | loop { 16 | match readline.get() { 17 | Some(line) => { 18 | if line.len() > 0 { 19 | let result = rep(line, repl_env.clone()); 20 | match result { 21 | Ok(str) => println!("{}", str), 22 | Err(MalError::BlankLine) => {} 23 | Err(err) => println!("{}", err), 24 | } 25 | } 26 | } 27 | None => break, 28 | } 29 | } 30 | readline.save_history(); 31 | } 32 | 33 | fn top_repl_env() -> Env { 34 | let repl_env = Env::new(None); 35 | for (name, func) in NS.iter() { 36 | repl_env.set( 37 | name, 38 | MalType::function(Function { 39 | func: Box::new(*func), 40 | env: None, 41 | }), 42 | ); 43 | } 44 | rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()).expect("could not define not"); 45 | repl_env 46 | } 47 | 48 | fn rep>(input: S, repl_env: Env) -> Result { 49 | let out = read(input.into())?; 50 | let out = eval(out, repl_env)?; 51 | let out = print(out); 52 | Ok(out) 53 | } 54 | 55 | fn read(code: String) -> MalResult { 56 | read_str(&code) 57 | } 58 | 59 | fn eval(mut ast: MalType, mut repl_env: Env) -> MalResult { 60 | loop { 61 | if ast.is_list() { 62 | if list_len(&ast) == 0 { 63 | return Ok(ast); 64 | } else { 65 | let result = if is_special_form(&ast) { 66 | process_special_form(&mut ast, repl_env.clone())? 67 | } else { 68 | eval_list(ast, repl_env.clone())? 69 | }; 70 | match result { 71 | TailPosition::Return(ret) => return Ok(ret), 72 | TailPosition::Call(new_ast, new_repl_env) => { 73 | ast = new_ast; 74 | if new_repl_env.is_some() { 75 | repl_env = new_repl_env.unwrap(); 76 | } 77 | } 78 | } 79 | } 80 | } else { 81 | return Ok(eval_ast(ast, repl_env.clone())?); 82 | } 83 | } 84 | } 85 | 86 | fn eval_list(ast: MalType, repl_env: Env) -> TailPositionResult { 87 | let new_ast = eval_ast(ast, repl_env)?; 88 | if let Some(vec) = new_ast.list_val() { 89 | if vec.len() > 0 { 90 | let mut vec = vec.clone(); 91 | let first = vec.remove(0); 92 | if let Some(Function { env, func, .. }) = first.function_val() { 93 | func(&mut vec, env.clone()).map(|r| TailPosition::Return(r)) 94 | } else if let Some(Lambda { 95 | env, args, body, .. 96 | }) = first.lambda_val() 97 | { 98 | call_lambda(env.clone(), args.clone(), body.clone(), vec) 99 | } else { 100 | Err(MalError::NotAFunction(first.clone())) 101 | } 102 | } else { 103 | panic!("Eval'd list is empty!") 104 | } 105 | } else { 106 | panic!("Eval'd list is no longer a list!") 107 | } 108 | } 109 | 110 | fn eval_ast(ast: MalType, repl_env: Env) -> MalResult { 111 | if let Some(symbol) = ast.symbol_val() { 112 | if let Ok(val) = repl_env.get(&symbol) { 113 | return Ok(val.clone()); 114 | } else { 115 | return Err(MalError::SymbolUndefined(symbol.to_string())); 116 | } 117 | } else if let Some(vec) = ast.list_or_vector_val() { 118 | let results: Result, MalError> = vec.into_iter() 119 | .map(|item| eval(item.clone(), repl_env.clone())) 120 | .collect(); 121 | if ast.is_list() { 122 | return Ok(MalType::list(results?)); 123 | } else { 124 | return Ok(MalType::vector(results?)); 125 | } 126 | } else if let Some(map) = ast.hashmap_val() { 127 | let mut new_map = BTreeMap::new(); 128 | for (key, val) in map { 129 | new_map.insert(key.clone(), eval(val.clone(), repl_env.clone())?); 130 | } 131 | let mut map = MalType::hashmap(new_map); 132 | return Ok(map); 133 | }; 134 | Ok(ast) 135 | } 136 | 137 | fn print(ast: MalType) -> String { 138 | pr_str(&ast, true) 139 | } 140 | 141 | fn list_len(list: &MalType) -> usize { 142 | if let Some(vec) = list.list_or_vector_val() { 143 | vec.len() 144 | } else { 145 | panic!("Expected a list but got: {:?}", list) 146 | } 147 | } 148 | 149 | fn call_lambda( 150 | outer_env: Env, 151 | binds: Vec, 152 | mut body: Vec, 153 | args: Vec, 154 | ) -> TailPositionResult { 155 | let env = Env::with_binds(Some(&outer_env), binds, args); 156 | let expr = body.remove(0); 157 | Ok(TailPosition::Call(expr, Some(env))) 158 | } 159 | 160 | fn is_special_form(ast: &MalType) -> bool { 161 | if let Some(vec) = ast.list_val() { 162 | if let Some(sym) = vec[0].symbol_val() { 163 | match sym { 164 | "def!" | "let*" | "do" | "if" | "fn*" => return true, 165 | _ => {} 166 | } 167 | } 168 | } 169 | false 170 | } 171 | 172 | fn process_special_form(ast: &mut MalType, repl_env: Env) -> TailPositionResult { 173 | if let Some(vec) = ast.list_val() { 174 | let mut vec = vec.clone(); 175 | if let Some(special) = vec.remove(0).symbol_val() { 176 | return match special { 177 | "def!" => special_def(&mut vec, repl_env), 178 | "let*" => special_let(&mut vec, repl_env), 179 | "do" => special_do(&mut vec, repl_env), 180 | "if" => special_if(&mut vec, repl_env), 181 | "fn*" => special_fn(&mut vec, repl_env), 182 | _ => panic!(format!("Unhandled special form: {}", special)), 183 | }; 184 | } 185 | } 186 | panic!("Expected a List for a special form!") 187 | } 188 | 189 | fn special_def(vec: &mut Vec, repl_env: Env) -> TailPositionResult { 190 | let name = vec.remove(0); 191 | if let Some(sym) = name.symbol_val() { 192 | let val = eval(vec.remove(0), repl_env.clone())?; 193 | repl_env.set(sym, val.clone()); 194 | Ok(TailPosition::Return(val)) 195 | } else { 196 | Err(MalError::WrongArguments(format!( 197 | "Expected a symbol as the first argument to def! but got: {:?}", 198 | name 199 | ))) 200 | } 201 | } 202 | 203 | fn special_let(vec: &mut Vec, repl_env: Env) -> TailPositionResult { 204 | let inner_repl_env = Env::new(Some(&repl_env)); 205 | let bindings = vec.remove(0); 206 | if let Some(bindings) = bindings.list_or_vector_val() { 207 | if bindings.len() % 2 != 0 { 208 | return Err(MalError::Parse( 209 | "Odd number of let* binding values!".to_string(), 210 | )); 211 | } 212 | let mut bindings = bindings.clone(); 213 | loop { 214 | if bindings.len() == 0 { 215 | break; 216 | } 217 | if let Some(name) = bindings.remove(0).symbol_val() { 218 | let val = eval(bindings.remove(0), inner_repl_env.clone())?; 219 | inner_repl_env.set(name, val); 220 | } else { 221 | return Err(MalError::Parse("Expected symbol".to_string())); 222 | } 223 | } 224 | let rest = vec.remove(0); 225 | Ok(TailPosition::Call(rest, Some(inner_repl_env))) 226 | } else { 227 | Err(MalError::WrongArguments(format!( 228 | "Expected a vector or list as the first argument to let* but got: {:?}", 229 | bindings 230 | ))) 231 | } 232 | } 233 | 234 | fn special_do(list: &mut Vec, repl_env: Env) -> TailPositionResult { 235 | if list.len() > 0 { 236 | while list.len() >= 2 { 237 | eval(list.remove(0), repl_env.clone())?; 238 | } 239 | Ok(TailPosition::Call(list.remove(0), Some(repl_env))) 240 | } else { 241 | Ok(TailPosition::Return(MalType::nil())) 242 | } 243 | } 244 | 245 | fn special_if(list: &mut Vec, repl_env: Env) -> TailPositionResult { 246 | let condition = list[0].clone(); 247 | let result = eval(condition, repl_env)?; 248 | if result.is_falsey() { 249 | if list.len() >= 3 { 250 | Ok(TailPosition::Call(list[2].clone(), None)) 251 | } else { 252 | Ok(TailPosition::Return(MalType::nil())) 253 | } 254 | } else { 255 | Ok(TailPosition::Call(list[1].clone(), None)) 256 | } 257 | } 258 | 259 | fn special_fn(list: &mut Vec, repl_env: Env) -> TailPositionResult { 260 | let args = &list[0]; 261 | if let Some(args) = args.list_or_vector_val() { 262 | let mut args = args.clone(); 263 | let body = list[1].clone(); 264 | Ok(TailPosition::Return(MalType::lambda(Lambda { 265 | env: repl_env.clone(), 266 | args, 267 | body: vec![body], 268 | is_macro: false, 269 | }))) 270 | } else { 271 | Err(MalError::WrongArguments(format!( 272 | "Expected a vector as the first argument to fn* but got: {:?}", 273 | args 274 | ))) 275 | } 276 | } 277 | 278 | #[cfg(test)] 279 | mod tests { 280 | use super::*; 281 | 282 | #[test] 283 | fn test_tco() { 284 | let repl_env = top_repl_env(); 285 | rep( 286 | "(def! f 287 | (fn* [a i] 288 | (if (= i 0) 289 | a 290 | (f (+ a 1) (- i 1)))))", 291 | repl_env.clone(), 292 | ).unwrap(); 293 | let result = rep("(f 1 1000)", repl_env).unwrap(); 294 | assert_eq!("1001", result); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /rust/src/bin/step4_if_fn_do.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::env::Env; 4 | use mal_rust::printer::pr_str; 5 | use mal_rust::reader::read_str; 6 | use mal_rust::readline::Readline; 7 | use mal_rust::types::*; 8 | use mal_rust::core::NS; 9 | 10 | use std::collections::BTreeMap; 11 | 12 | fn main() { 13 | let mut readline = Readline::new("user> "); 14 | let mut repl_env = top_repl_env(); 15 | loop { 16 | match readline.get() { 17 | Some(line) => { 18 | if line.len() > 0 { 19 | let result = rep(line, &mut repl_env); 20 | match result { 21 | Ok(str) => println!("{}", str), 22 | Err(MalError::BlankLine) => {} 23 | Err(err) => println!("{}", err), 24 | } 25 | } 26 | } 27 | None => break, 28 | } 29 | } 30 | readline.save_history(); 31 | } 32 | 33 | fn top_repl_env() -> Env { 34 | let repl_env = Env::new(None); 35 | for (name, func) in NS.iter() { 36 | repl_env.set( 37 | name, 38 | MalType::function(Function { 39 | func: Box::new(*func), 40 | env: None, 41 | }), 42 | ); 43 | } 44 | rep( 45 | "(def! not (fn* (a) (if a false true)))".to_string(), 46 | &repl_env, 47 | ).expect("could not define not"); 48 | repl_env 49 | } 50 | 51 | fn rep(input: String, repl_env: &Env) -> Result { 52 | let out = read(input)?; 53 | let out = eval(out, repl_env)?; 54 | let out = print(out); 55 | Ok(out) 56 | } 57 | 58 | fn read(code: String) -> MalResult { 59 | read_str(&code) 60 | } 61 | 62 | fn eval(mut ast: MalType, repl_env: &Env) -> MalResult { 63 | if ast.is_list() { 64 | if list_len(&ast) == 0 { 65 | Ok(ast) 66 | } else if is_special_form(&ast) { 67 | process_special_form(&mut ast, repl_env) 68 | } else { 69 | let new_ast = eval_ast(ast, repl_env)?; 70 | if let Some(vec) = new_ast.list_val() { 71 | if vec.len() > 0 { 72 | let mut vec = vec.clone(); 73 | let first = vec.remove(0); 74 | if let Some(Function { func, .. }) = first.function_val() { 75 | func(&mut vec, None) 76 | } else if let Some(Lambda { 77 | env, args, body, .. 78 | }) = first.lambda_val() 79 | { 80 | call_lambda(env.clone(), args.clone(), body.clone(), vec) 81 | } else { 82 | Err(MalError::NotAFunction(first.clone())) 83 | } 84 | } else { 85 | panic!("Eval'd list is empty!") 86 | } 87 | } else { 88 | panic!("Eval'd list is no longer a list!") 89 | } 90 | } 91 | } else { 92 | Ok(eval_ast(ast, repl_env)?) 93 | } 94 | } 95 | 96 | fn print(ast: MalType) -> String { 97 | pr_str(&ast, true) 98 | } 99 | 100 | fn eval_ast(ast: MalType, repl_env: &Env) -> MalResult { 101 | if let Some(symbol) = ast.symbol_val() { 102 | if let Ok(val) = repl_env.get(&symbol) { 103 | return Ok(val.clone()); 104 | } else { 105 | return Err(MalError::SymbolUndefined(symbol.to_string())); 106 | } 107 | } else if let Some(vec) = ast.list_or_vector_val() { 108 | let results: Result, MalError> = vec.into_iter() 109 | .map(|item| eval(item.clone(), repl_env)) 110 | .collect(); 111 | if ast.is_list() { 112 | return Ok(MalType::list(results?)); 113 | } else { 114 | return Ok(MalType::vector(results?)); 115 | } 116 | } else if let Some(map) = ast.hashmap_val() { 117 | let mut new_map = BTreeMap::new(); 118 | for (key, val) in map { 119 | new_map.insert(key.clone(), eval(val.clone(), repl_env)?); 120 | } 121 | let mut map = MalType::hashmap(new_map); 122 | return Ok(map); 123 | }; 124 | Ok(ast) 125 | } 126 | 127 | fn list_len(list: &MalType) -> usize { 128 | if let Some(vec) = list.list_or_vector_val() { 129 | vec.len() 130 | } else { 131 | panic!("Expected a list but got: {:?}", list) 132 | } 133 | } 134 | 135 | fn call_lambda( 136 | outer_env: Env, 137 | binds: Vec, 138 | mut body: Vec, 139 | args: Vec, 140 | ) -> MalResult { 141 | let env = Env::with_binds(Some(&outer_env), binds, args); 142 | let expr = body.remove(0); 143 | eval(expr, &env) 144 | } 145 | 146 | fn is_special_form(ast: &MalType) -> bool { 147 | if let Some(vec) = ast.list_val() { 148 | if let Some(sym) = vec[0].symbol_val() { 149 | match sym { 150 | "def!" | "let*" | "do" | "if" | "fn*" => return true, 151 | _ => {} 152 | } 153 | } 154 | } 155 | false 156 | } 157 | 158 | fn process_special_form(ast: &mut MalType, repl_env: &Env) -> MalResult { 159 | if let Some(vec) = ast.list_val() { 160 | let mut vec = vec.clone(); 161 | if let Some(special) = vec.remove(0).symbol_val() { 162 | return match special { 163 | "def!" => special_def(&mut vec, repl_env), 164 | "let*" => special_let(&mut vec, repl_env), 165 | "do" => special_do(&mut vec, repl_env), 166 | "if" => special_if(&mut vec, repl_env), 167 | "fn*" => special_fn(&mut vec, repl_env), 168 | _ => panic!(format!("Unhandled special form: {}", special)), 169 | }; 170 | } 171 | } 172 | panic!("Expected a List for a special form!") 173 | } 174 | 175 | fn special_def(vec: &mut Vec, repl_env: &Env) -> MalResult { 176 | let name = vec.remove(0); 177 | if let Some(sym) = name.symbol_val() { 178 | let val = eval(vec.remove(0), repl_env)?; 179 | repl_env.set(sym, val.clone()); 180 | Ok(val) 181 | } else { 182 | Err(MalError::WrongArguments(format!( 183 | "Expected a symbol as the first argument to def! but got: {:?}", 184 | name 185 | ))) 186 | } 187 | } 188 | 189 | fn special_let(vec: &mut Vec, repl_env: &Env) -> MalResult { 190 | let inner_repl_env = Env::new(Some(&repl_env)); 191 | let bindings = vec.remove(0); 192 | if let Some(bindings) = bindings.list_or_vector_val() { 193 | if bindings.len() % 2 != 0 { 194 | return Err(MalError::Parse( 195 | "Odd number of let* binding values!".to_string(), 196 | )); 197 | } 198 | let mut bindings = bindings.clone(); 199 | loop { 200 | if bindings.len() == 0 { 201 | break; 202 | } 203 | if let Some(name) = bindings.remove(0).symbol_val() { 204 | let val = eval(bindings.remove(0), &inner_repl_env)?; 205 | inner_repl_env.set(name, val); 206 | } else { 207 | return Err(MalError::Parse("Expected symbol".to_string())); 208 | } 209 | } 210 | let rest = vec.remove(0); 211 | eval(rest, &inner_repl_env) 212 | } else { 213 | Err(MalError::WrongArguments(format!( 214 | "Expected a vector or list as the first argument to let* but got: {:?}", 215 | bindings 216 | ))) 217 | } 218 | } 219 | 220 | fn special_do(list: &mut Vec, repl_env: &Env) -> MalResult { 221 | let mut result = MalType::nil(); 222 | while list.len() > 0 { 223 | result = eval(list.remove(0), repl_env)?; 224 | } 225 | Ok(result) 226 | } 227 | 228 | fn special_if(list: &mut Vec, repl_env: &Env) -> MalResult { 229 | let condition = list[0].clone(); 230 | let result = eval(condition, repl_env)?; 231 | if result.is_falsey() { 232 | if list.len() >= 3 { 233 | eval(list[2].clone(), repl_env) 234 | } else { 235 | Ok(MalType::nil()) 236 | } 237 | } else { 238 | eval(list[1].clone(), repl_env) 239 | } 240 | } 241 | 242 | fn special_fn(list: &mut Vec, repl_env: &Env) -> MalResult { 243 | let args = &list[0]; 244 | if let Some(args) = args.list_or_vector_val() { 245 | let mut args = args.clone(); 246 | let body = list[1].clone(); 247 | Ok(MalType::lambda(Lambda { 248 | env: repl_env.clone(), 249 | args, 250 | body: vec![body], 251 | is_macro: false, 252 | })) 253 | } else { 254 | Err(MalError::WrongArguments(format!( 255 | "Expected a vector as the first argument to fn* but got: {:?}", 256 | args 257 | ))) 258 | } 259 | } 260 | 261 | #[cfg(test)] 262 | mod tests { 263 | use super::*; 264 | 265 | #[test] 266 | fn test_if() { 267 | let mut repl_env = top_repl_env(); 268 | let result = rep("(if 1 2 3)".to_string(), &mut repl_env).unwrap(); 269 | assert_eq!("2", result); 270 | let result = rep("(if false 2 3)".to_string(), &mut repl_env).unwrap(); 271 | assert_eq!("3", result); 272 | let result = rep("(if nil 2 (+ 2 3))".to_string(), &mut repl_env).unwrap(); 273 | assert_eq!("5", result); 274 | let result = rep("(if nil 2)".to_string(), &mut repl_env).unwrap(); 275 | assert_eq!("nil", result); 276 | } 277 | 278 | #[test] 279 | fn test_fn() { 280 | let mut repl_env = top_repl_env(); 281 | let result = rep("(fn* [a] a)".to_string(), &mut repl_env).unwrap(); 282 | assert_eq!("#", result); 283 | let result = rep("((fn* [a] a) 7)".to_string(), &mut repl_env).unwrap(); 284 | assert_eq!("7", result); 285 | let result = rep("((fn* [a b] (+ a b)) 2 3)".to_string(), &mut repl_env).unwrap(); 286 | assert_eq!("5", result); 287 | let result = rep( 288 | "((fn* [a & more] (count more)) 2 3 4)".to_string(), 289 | &mut repl_env, 290 | ).unwrap(); 291 | assert_eq!("2", result); 292 | let result = rep( 293 | "((fn* (a & more) (count more)) 2)".to_string(), 294 | &mut repl_env, 295 | ).unwrap(); 296 | assert_eq!("0", result); 297 | } 298 | 299 | #[test] 300 | fn test_do() { 301 | let mut repl_env = top_repl_env(); 302 | let result = rep("(do 1 (def! x (+ 1 2)) (* 2 3))".to_string(), &mut repl_env).unwrap(); 303 | assert_eq!("6", result); 304 | assert_eq!(MalType::number(3), repl_env.get("x").unwrap()); 305 | } 306 | 307 | #[test] 308 | fn test_list_and_vec_equal() { 309 | let mut repl_env = top_repl_env(); 310 | let result = rep( 311 | "(= [1 2 (list 3 4 [5 6])] (list 1 2 [3 4 (list 5 6)]))".to_string(), 312 | &mut repl_env, 313 | ).unwrap(); 314 | assert_eq!("true", result); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /mal.html: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | Mal Web REPL 26 | 27 | 28 |
29 |

Mal

30 | 31 |

Mal Web REPL

32 | 33 | 40 | 41 |
42 |
43 |
44 |
45 | 48 |

 

49 |
50 |
51 | 54 |
55 | 56 |
57 | 58 |
59 |

Mal at a glance

60 |
61 | 62 |
63 |
64 |

Datatypes

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
Maps{"key1" "val1", "key2" 123}
Lists(1 2 3 "four")
Vectors[1 2 3 4 "a" "b" "c" 1 2]
Scalarsa-symbol, "a string", :a_keyword, 123, nil, true, false
83 |
84 |
85 |

Functions

86 | 87 | 88 | 89 | 91 | 92 | 93 | 94 | 98 | 99 | 100 | 101 | 104 | 105 |
Calling(<function> 90 | <args*>)
Defining named functions(def! <name> 95 | (fn* 96 | [<args*>] 97 | <action>))
Anonymous function(fn* 102 | [<args*>] 103 | <action>)
106 |
107 |
108 |

Useful Macros and Special Forms

109 | 110 | 111 | 112 | 113 | 114 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
Conditionalsif cond or
Multiple Actions (side-effects)(do 123 | <action*>...)
Defining thingsdef! defmacro! let*
Quoting' ` ~ ~@
Examining macrosmacroexpand
138 |
139 |
140 | 141 |
142 |
143 |

Useful Functions

144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
Math+ - * /
Comparison/Boolean= < > <= >= not
Predicatesnil? true? false? symbol? keyword? string? list? vector? map? sequential?
Data processingmap apply
Data createlist vector hash-map
Data inspectionfirst rest get keys vals count get nth contains? empty?
Data manipulationconj cons concat assoc dissoc
Lists and Vectorsfirst rest nth seq
Hash Mapsget keys vals contains?
Stringsstr pr-str seq
Atomsatom atom? deref[@] reset! swap!
Metameta with-meta[^]
Outputprintln prn
198 |
199 |
200 |

JavaScript Interop

201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
Evaluate JavaScript(js-eval "JS string to eval")
Method call/access(. js-fn arg...)
211 |
212 |
213 | 214 |
215 | 216 | 220 | 221 |
222 | 223 | 224 | 225 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /rust/src/reader.rs: -------------------------------------------------------------------------------- 1 | extern crate regex; 2 | use regex::Regex; 3 | 4 | use types::*; 5 | 6 | use std::collections::BTreeMap; 7 | 8 | macro_rules! consume_and_assert_eq { 9 | ( $reader:expr, $expected:expr ) => { 10 | { 11 | let token = $reader.next().expect( 12 | &format!("Expected {:?} but got None!", &$expected) 13 | ); 14 | if token != $expected { 15 | panic!(format!("Expected {:?} but got {:?}", &$expected, &token)); 16 | } 17 | } 18 | }; 19 | } 20 | 21 | pub struct Reader { 22 | tokens: Vec, 23 | position: usize, 24 | } 25 | 26 | impl Reader { 27 | pub fn peek(&self) -> Option { 28 | if self.tokens.len() > self.position { 29 | Some(self.tokens[self.position].to_owned()) 30 | } else { 31 | None 32 | } 33 | } 34 | 35 | pub fn next(&mut self) -> Option { 36 | if let Some(token) = self.peek() { 37 | self.position += 1; 38 | Some(token) 39 | } else { 40 | None 41 | } 42 | } 43 | } 44 | 45 | pub fn read_str(code: &str) -> MalResult { 46 | let tokens = tokenizer(code); 47 | let mut reader = Reader { 48 | tokens: tokens, 49 | position: 0, 50 | }; 51 | read_form(&mut reader) 52 | } 53 | 54 | const TOKEN_MATCH: &str = r#"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"#; 55 | 56 | fn tokenizer(code: &str) -> Vec { 57 | let re = Regex::new(TOKEN_MATCH).unwrap(); 58 | let mut tokens: Vec = vec![]; 59 | for token_match in re.captures_iter(code) { 60 | tokens.push(token_match[1].to_string()); 61 | } 62 | tokens 63 | } 64 | 65 | fn read_form(reader: &mut Reader) -> MalResult { 66 | let token = reader.peek().unwrap(); 67 | if token.len() == 0 { 68 | return Err(MalError::Parse("unexpected EOF".to_string())); 69 | } 70 | let mut chars = token.chars(); 71 | match chars.next().unwrap() { 72 | ';' => { 73 | reader.next(); 74 | Err(MalError::BlankLine) 75 | } 76 | '(' => read_list(reader), 77 | '[' => read_vector(reader), 78 | '{' => read_hash_map(reader), 79 | '"' => read_string(reader), 80 | ':' => read_keyword(reader), 81 | '\'' => read_quote(reader, "quote"), 82 | '~' => { 83 | if let Some('@') = chars.next() { 84 | read_quote(reader, "splice-unquote") 85 | } else { 86 | read_quote(reader, "unquote") 87 | } 88 | } 89 | '`' => read_quote(reader, "quasiquote"), 90 | '@' => read_quote(reader, "deref"), 91 | '^' => read_with_meta(reader), 92 | _ => read_atom(reader), 93 | } 94 | } 95 | 96 | fn read_string(reader: &mut Reader) -> MalResult { 97 | let token = reader.next().unwrap(); 98 | let mut chars = token.chars(); 99 | if chars.next().unwrap() != '"' { 100 | panic!("Expected start of a string!") 101 | } 102 | let mut str = String::new(); 103 | loop { 104 | match chars.next() { 105 | Some('"') => break, 106 | Some('\\') => str.push(unescape_char(chars.next())?), 107 | Some(c) => str.push(c), 108 | None => return Err(MalError::Parse("Unexpected end of string!".to_string())), 109 | } 110 | } 111 | Ok(MalType::string(str)) 112 | } 113 | 114 | fn read_keyword(reader: &mut Reader) -> MalResult { 115 | let token = reader.next().unwrap(); 116 | Ok(MalType::keyword(token[1..].to_string())) 117 | } 118 | 119 | fn read_quote(reader: &mut Reader, expanded: &str) -> MalResult { 120 | reader.next().unwrap(); 121 | let value = read_form(reader).unwrap(); 122 | let list = MalType::list(vec![MalType::symbol(expanded), value]); 123 | Ok(list) 124 | } 125 | 126 | fn read_with_meta(reader: &mut Reader) -> MalResult { 127 | consume_and_assert_eq!(reader, "^"); 128 | let metadata = read_form(reader)?; 129 | let value = read_form(reader)?; 130 | let list = MalType::list(vec![MalType::symbol("with-meta"), value, metadata]); 131 | Ok(list) 132 | } 133 | 134 | fn unescape_char(char: Option) -> Result { 135 | match char { 136 | Some('n') => Ok('\n'), 137 | Some(c) => Ok(c), 138 | None => Err(MalError::Parse("Unexpected end of string!".to_string())), 139 | } 140 | } 141 | 142 | fn read_list(reader: &mut Reader) -> MalResult { 143 | consume_and_assert_eq!(reader, "("); 144 | let list = read_list_inner(reader, ")")?; 145 | Ok(MalType::list(list)) 146 | } 147 | 148 | fn read_vector(reader: &mut Reader) -> MalResult { 149 | consume_and_assert_eq!(reader, "["); 150 | let list = read_list_inner(reader, "]")?; 151 | Ok(MalType::vector(list)) 152 | } 153 | 154 | fn read_hash_map(reader: &mut Reader) -> MalResult { 155 | consume_and_assert_eq!(reader, "{"); 156 | let list = read_list_inner(reader, "}")?; 157 | if list.len() % 2 != 0 { 158 | return Err(MalError::Parse("Odd number of hash-map items!".to_string())); 159 | } 160 | let mut map = BTreeMap::new(); 161 | let mut list_iter = list.into_iter(); 162 | loop { 163 | if let Some(key) = list_iter.next() { 164 | let val = list_iter.next().unwrap(); 165 | map.insert(key, val); 166 | } else { 167 | break; 168 | } 169 | } 170 | Ok(MalType::hashmap(map)) 171 | } 172 | 173 | fn read_list_inner(reader: &mut Reader, close: &str) -> Result, MalError> { 174 | let mut list: Vec = Vec::new(); 175 | loop { 176 | if let Some(token) = reader.peek() { 177 | if token == close { 178 | reader.next(); 179 | break; 180 | } 181 | match read_form(reader) { 182 | Err(MalError::BlankLine) => {} 183 | Err(other) => return Err(other), 184 | Ok(form) => list.push(form), 185 | } 186 | } else { 187 | return Err(MalError::Parse("EOF while reading list".to_string())); 188 | } 189 | } 190 | Ok(list) 191 | } 192 | 193 | const NUMBER_MATCH: &str = r#"^\-?[\d\.]+$"#; 194 | 195 | fn read_atom(reader: &mut Reader) -> MalResult { 196 | let token = reader.next().unwrap(); 197 | let num_re = Regex::new(NUMBER_MATCH).unwrap(); 198 | let value = if num_re.is_match(&token) { 199 | MalType::number(token.parse::().unwrap_or(0)) 200 | } else { 201 | match token.as_ref() { 202 | "nil" => MalType::nil(), 203 | "true" => MalType::bool_true(), 204 | "false" => MalType::bool_false(), 205 | _ => MalType::symbol(token), 206 | } 207 | }; 208 | Ok(value) 209 | } 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | use super::*; 214 | 215 | #[test] 216 | fn test_tokenizer() { 217 | let code = "(+ 2 (* 3 4))"; 218 | let tokens = tokenizer(code); 219 | assert_eq!( 220 | tokens, 221 | vec![ 222 | "(".to_string(), 223 | "+".to_string(), 224 | "2".to_string(), 225 | "(".to_string(), 226 | "*".to_string(), 227 | "3".to_string(), 228 | "4".to_string(), 229 | ")".to_string(), 230 | ")".to_string(), 231 | ] 232 | ); 233 | } 234 | 235 | #[test] 236 | fn test_read_str() { 237 | let code = "(nil true false :foo \"string\" (+ 2 (* 3 4)))"; 238 | let ast = read_str(code).unwrap(); 239 | assert_eq!( 240 | ast, 241 | MalType::list(vec![ 242 | MalType::nil(), 243 | MalType::bool_true(), 244 | MalType::bool_false(), 245 | MalType::keyword("foo"), 246 | MalType::string("string"), 247 | MalType::list(vec![ 248 | MalType::symbol("+"), 249 | MalType::number(2), 250 | MalType::list(vec![ 251 | MalType::symbol("*"), 252 | MalType::number(3), 253 | MalType::number(4), 254 | ]), 255 | ]), 256 | ]) 257 | ); 258 | } 259 | 260 | #[test] 261 | fn test_read_vector() { 262 | let code = "[1 :foo nil]"; 263 | let ast = read_str(code).unwrap(); 264 | assert_eq!( 265 | ast, 266 | MalType::vector(vec![ 267 | MalType::number(1), 268 | MalType::keyword("foo"), 269 | MalType::nil(), 270 | ]) 271 | ); 272 | } 273 | 274 | #[test] 275 | fn test_hash_map() { 276 | let code = "{:foo 1 \"bar\" [2 3]}"; 277 | let ast = read_str(code).unwrap(); 278 | let mut map = BTreeMap::new(); 279 | map.insert(MalType::keyword("foo"), MalType::number(1)); 280 | map.insert( 281 | MalType::string("bar"), 282 | MalType::vector(vec![MalType::number(2), MalType::number(3)]), 283 | ); 284 | assert_eq!(ast, MalType::hashmap(map)); 285 | } 286 | 287 | #[test] 288 | fn test_unclosed_string() { 289 | let code = "\"abc"; 290 | let err = read_str(code).unwrap_err(); 291 | assert_eq!(err, MalError::Parse("unexpected EOF".to_string())); 292 | } 293 | 294 | #[test] 295 | fn test_quote() { 296 | let code = "('foo ~bar `baz ~@fuz @buz)"; 297 | let ast = read_str(code).unwrap(); 298 | assert_eq!( 299 | ast, 300 | MalType::list(vec![ 301 | MalType::list(vec![MalType::symbol("quote"), MalType::symbol("foo")]), 302 | MalType::list(vec![MalType::symbol("unquote"), MalType::symbol("bar")]), 303 | MalType::list(vec![MalType::symbol("quasiquote"), MalType::symbol("baz")]), 304 | MalType::list(vec![ 305 | MalType::symbol("splice-unquote"), 306 | MalType::symbol("fuz"), 307 | ]), 308 | MalType::list(vec![MalType::symbol("deref"), MalType::symbol("buz")]), 309 | ]) 310 | ); 311 | } 312 | 313 | #[test] 314 | fn test_with_meta() { 315 | let code = "^{\"a\" 1} [1 2 3]"; 316 | let ast = read_str(code).unwrap(); 317 | let mut map = BTreeMap::new(); 318 | map.insert(MalType::string("a"), MalType::number(1)); 319 | assert_eq!( 320 | ast, 321 | MalType::list(vec![ 322 | MalType::symbol("with-meta"), 323 | MalType::vector(vec![ 324 | MalType::number(1), 325 | MalType::number(2), 326 | MalType::number(3), 327 | ]), 328 | MalType::hashmap(map), 329 | ]) 330 | ); 331 | } 332 | 333 | #[test] 334 | fn test_comment() { 335 | let code = "; comment"; 336 | let err = read_str(code).unwrap_err(); 337 | assert_eq!(err, MalError::BlankLine); 338 | let code = "[1] ; comment"; 339 | let ast = read_str(code).unwrap(); 340 | assert_eq!(ast, MalType::vector(vec![MalType::number(1)])); 341 | let code = "\"str\" ; comment"; 342 | let ast = read_str(code).unwrap(); 343 | assert_eq!(ast, MalType::string("str")); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /rust/src/bin/step6_file.rs: -------------------------------------------------------------------------------- 1 | extern crate mal_rust; 2 | 3 | use mal_rust::env::Env; 4 | use mal_rust::printer::pr_str; 5 | use mal_rust::reader::read_str; 6 | use mal_rust::readline::Readline; 7 | use mal_rust::types::*; 8 | use mal_rust::core::NS; 9 | 10 | use std::collections::BTreeMap; 11 | use std::env; 12 | use std::process; 13 | 14 | fn main() { 15 | let mut readline = Readline::new("user> "); 16 | let repl_env = top_repl_env(); 17 | let args: Vec<_> = env::args().collect(); 18 | if args.len() > 1 { 19 | let result = rep( 20 | "(load-file \"".to_string() + &args[1] + "\")", 21 | repl_env.clone(), 22 | ); 23 | match result { 24 | Err(err) => { 25 | println!("{}", err); 26 | process::exit(1); 27 | } 28 | _ => process::exit(0), 29 | } 30 | } 31 | loop { 32 | match readline.get() { 33 | Some(line) => { 34 | if line.len() > 0 { 35 | let result = rep(line, repl_env.clone()); 36 | match result { 37 | Ok(str) => println!("{}", str), 38 | Err(MalError::BlankLine) => {} 39 | Err(err) => println!("{}", err), 40 | } 41 | } 42 | } 43 | None => break, 44 | } 45 | } 46 | readline.save_history(); 47 | } 48 | 49 | fn top_repl_env() -> Env { 50 | let repl_env = Env::new(None); 51 | for (name, func) in NS.iter() { 52 | repl_env.set( 53 | name, 54 | MalType::function(Function { 55 | func: Box::new(*func), 56 | env: Some(repl_env.clone()), 57 | }), 58 | ); 59 | } 60 | repl_env.set( 61 | "eval", 62 | MalType::function(Function { 63 | func: Box::new(eval_fn), 64 | env: Some(repl_env.clone()), 65 | }), 66 | ); 67 | let argv: Vec<_> = env::args().collect(); 68 | repl_env.set( 69 | "*ARGV*", 70 | MalType::list(if argv.len() >= 3 { 71 | argv[2..] 72 | .iter() 73 | .map(|a| MalType::string(a.clone())) 74 | .collect() 75 | } else { 76 | vec![] 77 | }), 78 | ); 79 | rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()).expect("could not define not"); 80 | rep( 81 | "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", 82 | repl_env.clone(), 83 | ).expect("could not define load-file"); 84 | repl_env 85 | } 86 | 87 | fn eval_fn(args: &mut Vec, repl_env: Option) -> MalResult { 88 | eval(args.remove(0), repl_env.unwrap()) 89 | } 90 | 91 | fn rep>(input: S, repl_env: Env) -> Result { 92 | let out = read(input.into())?; 93 | let out = eval(out, repl_env)?; 94 | let out = print(out); 95 | Ok(out) 96 | } 97 | 98 | fn read(code: String) -> MalResult { 99 | read_str(&code) 100 | } 101 | 102 | fn eval(mut ast: MalType, mut repl_env: Env) -> MalResult { 103 | loop { 104 | if ast.is_list() { 105 | if list_len(&ast) == 0 { 106 | return Ok(ast); 107 | } else { 108 | let result = if is_special_form(&ast) { 109 | process_special_form(&mut ast, repl_env.clone())? 110 | } else { 111 | eval_list(ast, repl_env.clone())? 112 | }; 113 | match result { 114 | TailPosition::Return(ret) => return Ok(ret), 115 | TailPosition::Call(new_ast, new_repl_env) => { 116 | ast = new_ast; 117 | if new_repl_env.is_some() { 118 | repl_env = new_repl_env.unwrap(); 119 | } 120 | } 121 | } 122 | } 123 | } else { 124 | return Ok(eval_ast(ast, repl_env.clone())?); 125 | } 126 | } 127 | } 128 | 129 | fn eval_list(ast: MalType, repl_env: Env) -> TailPositionResult { 130 | let new_ast = eval_ast(ast, repl_env)?; 131 | if let Some(vec) = new_ast.list_val() { 132 | if vec.len() > 0 { 133 | let mut vec = vec.clone(); 134 | let first = vec.remove(0); 135 | if let Some(Function { env, func, .. }) = first.function_val() { 136 | func(&mut vec, env.clone()).map(|r| TailPosition::Return(r)) 137 | } else if let Some(Lambda { 138 | env, args, body, .. 139 | }) = first.lambda_val() 140 | { 141 | call_lambda(env.clone(), args.clone(), body.clone(), vec) 142 | } else { 143 | Err(MalError::NotAFunction(first.clone())) 144 | } 145 | } else { 146 | panic!("Eval'd list is empty!") 147 | } 148 | } else { 149 | panic!("Eval'd list is no longer a list!") 150 | } 151 | } 152 | 153 | fn eval_ast(ast: MalType, repl_env: Env) -> MalResult { 154 | if let Some(symbol) = ast.symbol_val() { 155 | if let Ok(val) = repl_env.get(&symbol) { 156 | return Ok(val.clone()); 157 | } else { 158 | return Err(MalError::SymbolUndefined(symbol.to_string())); 159 | } 160 | } else if let Some(vec) = ast.list_or_vector_val() { 161 | let results: Result, MalError> = vec.into_iter() 162 | .map(|item| eval(item.clone(), repl_env.clone())) 163 | .collect(); 164 | if ast.is_list() { 165 | return Ok(MalType::list(results?)); 166 | } else { 167 | return Ok(MalType::vector(results?)); 168 | } 169 | } else if let Some(map) = ast.hashmap_val() { 170 | let mut new_map = BTreeMap::new(); 171 | for (key, val) in map { 172 | new_map.insert(key.clone(), eval(val.clone(), repl_env.clone())?); 173 | } 174 | let mut map = MalType::hashmap(new_map); 175 | return Ok(map); 176 | }; 177 | Ok(ast) 178 | } 179 | 180 | fn print(ast: MalType) -> String { 181 | pr_str(&ast, true) 182 | } 183 | 184 | fn list_len(list: &MalType) -> usize { 185 | if let Some(vec) = list.list_or_vector_val() { 186 | vec.len() 187 | } else { 188 | panic!("Expected a list but got: {:?}", list) 189 | } 190 | } 191 | 192 | fn call_lambda( 193 | outer_env: Env, 194 | binds: Vec, 195 | mut body: Vec, 196 | args: Vec, 197 | ) -> TailPositionResult { 198 | let env = Env::with_binds(Some(&outer_env), binds, args); 199 | let expr = body.remove(0); 200 | Ok(TailPosition::Call(expr, Some(env))) 201 | } 202 | 203 | fn is_special_form(ast: &MalType) -> bool { 204 | if let Some(vec) = ast.list_val() { 205 | if let Some(sym) = vec[0].symbol_val() { 206 | match sym { 207 | "def!" | "let*" | "do" | "if" | "fn*" => return true, 208 | _ => {} 209 | } 210 | } 211 | } 212 | false 213 | } 214 | 215 | fn process_special_form(ast: &mut MalType, repl_env: Env) -> TailPositionResult { 216 | if let Some(vec) = ast.list_val() { 217 | let mut vec = vec.clone(); 218 | if let Some(special) = vec.remove(0).symbol_val() { 219 | return match special { 220 | "def!" => special_def(&mut vec, repl_env), 221 | "let*" => special_let(&mut vec, repl_env), 222 | "do" => special_do(&mut vec, repl_env), 223 | "if" => special_if(&mut vec, repl_env), 224 | "fn*" => special_fn(&mut vec, repl_env), 225 | _ => panic!(format!("Unhandled special form: {}", special)), 226 | }; 227 | } 228 | } 229 | panic!("Expected a List for a special form!") 230 | } 231 | 232 | fn special_def(vec: &mut Vec, repl_env: Env) -> TailPositionResult { 233 | let name = vec.remove(0); 234 | if let Some(sym) = name.symbol_val() { 235 | let val = eval(vec.remove(0), repl_env.clone())?; 236 | repl_env.set(sym, val.clone()); 237 | Ok(TailPosition::Return(val)) 238 | } else { 239 | Err(MalError::WrongArguments(format!( 240 | "Expected a symbol as the first argument to def! but got: {:?}", 241 | name 242 | ))) 243 | } 244 | } 245 | 246 | fn special_let(vec: &mut Vec, repl_env: Env) -> TailPositionResult { 247 | let inner_repl_env = Env::new(Some(&repl_env)); 248 | let bindings = vec.remove(0); 249 | if let Some(bindings) = bindings.list_or_vector_val() { 250 | if bindings.len() % 2 != 0 { 251 | return Err(MalError::Parse( 252 | "Odd number of let* binding values!".to_string(), 253 | )); 254 | } 255 | let mut bindings = bindings.clone(); 256 | loop { 257 | if bindings.len() == 0 { 258 | break; 259 | } 260 | if let Some(name) = bindings.remove(0).symbol_val() { 261 | let val = eval(bindings.remove(0), inner_repl_env.clone())?; 262 | inner_repl_env.set(name, val); 263 | } else { 264 | return Err(MalError::Parse("Expected symbol".to_string())); 265 | } 266 | } 267 | let rest = vec.remove(0); 268 | Ok(TailPosition::Call(rest, Some(inner_repl_env))) 269 | } else { 270 | Err(MalError::WrongArguments(format!( 271 | "Expected a vector or list as the first argument to let* but got: {:?}", 272 | bindings 273 | ))) 274 | } 275 | } 276 | 277 | fn special_do(list: &mut Vec, repl_env: Env) -> TailPositionResult { 278 | if list.len() > 0 { 279 | while list.len() >= 2 { 280 | eval(list.remove(0), repl_env.clone())?; 281 | } 282 | Ok(TailPosition::Call(list.remove(0), Some(repl_env))) 283 | } else { 284 | Ok(TailPosition::Return(MalType::nil())) 285 | } 286 | } 287 | 288 | fn special_if(list: &mut Vec, repl_env: Env) -> TailPositionResult { 289 | let condition = list[0].clone(); 290 | let result = eval(condition, repl_env)?; 291 | if result.is_falsey() { 292 | if list.len() >= 3 { 293 | Ok(TailPosition::Call(list[2].clone(), None)) 294 | } else { 295 | Ok(TailPosition::Return(MalType::nil())) 296 | } 297 | } else { 298 | Ok(TailPosition::Call(list[1].clone(), None)) 299 | } 300 | } 301 | 302 | fn special_fn(list: &mut Vec, repl_env: Env) -> TailPositionResult { 303 | let args = &list[0]; 304 | if let Some(args) = args.list_or_vector_val() { 305 | let mut args = args.clone(); 306 | let body = list[1].clone(); 307 | Ok(TailPosition::Return(MalType::lambda(Lambda { 308 | env: repl_env.clone(), 309 | args, 310 | body: vec![body], 311 | is_macro: false, 312 | }))) 313 | } else { 314 | Err(MalError::WrongArguments(format!( 315 | "Expected a vector as the first argument to fn* but got: {:?}", 316 | args 317 | ))) 318 | } 319 | } 320 | 321 | #[cfg(test)] 322 | mod tests { 323 | use super::*; 324 | 325 | #[test] 326 | fn test_atom() { 327 | let repl_env = top_repl_env(); 328 | rep("(def! a (atom 1))", repl_env.clone()).unwrap(); 329 | let a = &repl_env.get("a").unwrap(); 330 | assert_eq!("(atom 1)", print(a.clone())); 331 | rep("(reset! a 2)", repl_env.clone()).unwrap(); 332 | assert_eq!("(atom 2)", print(a.clone())); 333 | let result = rep("(deref a)", repl_env.clone()).unwrap(); 334 | assert_eq!("2", result); 335 | assert_eq!("(atom 2)", print(a.clone())); 336 | rep("(swap! a + 2)", repl_env.clone()).unwrap(); 337 | assert_eq!("(atom 4)", print(a.clone())); 338 | } 339 | 340 | #[test] 341 | fn test_load_file() { 342 | let repl_env = top_repl_env(); 343 | let result = rep("(load-file \"../tests/incB.mal\")", repl_env.clone()).unwrap(); 344 | assert_eq!("\"incB.mal return string\"", result); 345 | } 346 | } 347 | --------------------------------------------------------------------------------