diff --git a/.travis.yml b/.travis.yml index 8ab564836f..198a926f31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: - {env: IMPL=js, services: [docker]} - {env: IMPL=julia, services: [docker]} - {env: IMPL=kotlin, services: [docker]} + - {env: IMPL=logo, services: [docker]} - {env: IMPL=lua, services: [docker]} - {env: IMPL=make, services: [docker]} - {env: IMPL=mal BUILD_IMPL=js NO_PERF=1, services: [docker]} diff --git a/Makefile b/Makefile index 9272606aeb..a64855089a 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ DEFERRABLE=1 OPTIONAL=1 # Extra implementation specific options to pass to runtest.py +logo_TEST_OPTS = --start-timeout 60 --test-timeout 120 mal_TEST_OPTS = --start-timeout 60 --test-timeout 120 miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120 plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180 @@ -78,7 +79,7 @@ DOCKERIZE = IMPLS = ada awk bash c d clojure coffee cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ - io java julia js kotlin lua make mal ocaml matlab miniMAL \ + io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php plpgsql plsql ps python r \ racket rpython ruby rust scala swift swift3 tcl vb vhdl vimscript @@ -107,6 +108,7 @@ regress_step9 = $(regress_step8) step9 regress_stepA = $(regress_step9) stepA test_EXCLUDES += test^bash^step5 # never completes at 10,000 +test_EXCLUDES += test^logo^step5 # too slow for 10,000 test_EXCLUDES += test^make^step5 # no TCO capability (iteration or recursion) test_EXCLUDES += test^mal^step5 # host impl dependent test_EXCLUDES += test^matlab^step5 # never completes at 10,000 diff --git a/README.md b/README.md index 42e084ae1a..aa6cf26767 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 55 languages: +Mal is implemented in 56 languages: * Ada * GNU awk @@ -35,6 +35,7 @@ Mal is implemented in 55 languages: * JavaScript ([Online Demo](http://kanaka.github.io/mal)) * Julia * Kotlin +* Logo * Lua * GNU Make * mal itself @@ -448,6 +449,17 @@ make java -jar stepX_YYY.jar ``` +### Logo + +*The Logo implementation was created by [Dov Murik](https://github.com/dubek)* + +The Logo implementation of mal has been tested with UCBLogo 6.0. + +``` +cd logo +logo stepX_YYY.lg +``` + ### Lua Running the Lua implementation of mal requires lua 5.1 or later, diff --git a/logo/Dockerfile b/logo/Dockerfile new file mode 100644 index 0000000000..c9ca5c27f7 --- /dev/null +++ b/logo/Dockerfile @@ -0,0 +1,49 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Install g++ for any C/C++ based implementations +RUN apt-get -y install g++ + +# Install UCBLogo 6.0: +# * Fix the makefile to build correctly +# * Tweat GC settings to improve performance (it's still very slow) +# * Add the timems function implemented in C +RUN apt-get -y install libx11-dev \ + && cd /tmp \ + && curl -O -J -L http://www.cs.berkeley.edu/~bh/downloads/ucblogo.tar.gz \ + && tar xf ucblogo.tar.gz \ + && cd /tmp/ucblogo-6.0 \ + && rm -rf csls/CVS \ + && ./configure \ + && sed -i -e 's/svnversion/echo 206/' -e 's/^\s*(cd docs/#\0/' makefile \ + && echo "all: everything" >> makefile \ + && sed -i -e 's/^#define *SEG_SIZE *16000 /#define SEG_SIZE 6400000 /' logo.h \ + && sed -i -e 's/^#define GCMAX 16000$/#define GCMAX 16000000/' mem.c \ + && echo "extern NODE *ltimems(NODE *);" >> globals.h \ + && echo "NODE *ltimems(NODE *args) { struct timeval tv; gettimeofday(&tv, NULL); return(make_floatnode(((FLONUM)tv.tv_sec) * 1000.0 + (tv.tv_usec / 1000))); }" >> coms.c \ + && sed -i -e 's/^\(.*lthrow.*\)$/\1 {"timems", 0, 0, 0, PREFIX_PRIORITY, ltimems},/' init.c \ + && make install \ + && cd /tmp \ + && rm -rf /tmp/ucblogo.tar.gz /tmp/ucblogo-6.0 + +ENV HOME /mal diff --git a/logo/Makefile b/logo/Makefile new file mode 100644 index 0000000000..2beda63bde --- /dev/null +++ b/logo/Makefile @@ -0,0 +1,28 @@ +SOURCES_BASE = readline.lg types.lg reader.lg printer.lg +SOURCES_LISP = env.lg core.lg stepA_mal.lg +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +.PHONY: all dist clean stats stats-lisp + +all: + @true + +dist: mal.lg mal + +mal.lg: $(SOURCES) + cat $+ | grep -v "^load " > $@ + +mal: mal.lg + echo "#!/usr/bin/env logo" > $@ + cat $< >> $@ + chmod +x $@ + +clean: + rm -f mal.lg mal + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/logo/core.lg b/logo/core.lg new file mode 100644 index 0000000000..2d807fd662 --- /dev/null +++ b/logo/core.lg @@ -0,0 +1,413 @@ +load "../logo/types.lg +load "../logo/reader.lg +load "../logo/printer.lg + +make "global_exception [] + +to bool_to_mal :bool +output ifelse :bool [true_new] [false_new] +end + +to mal_equal_q :a :b +output bool_to_mal equal_q :a :b +end + +to mal_throw :a +make "global_exception :a +(throw "error "_mal_exception_) +end + +to mal_nil_q :a +output bool_to_mal ((obj_type :a) = "nil) +end + +to mal_true_q :a +output bool_to_mal ((obj_type :a) = "true) +end + +to mal_false_q :a +output bool_to_mal ((obj_type :a) = "false) +end + +to mal_string_q :a +output bool_to_mal ((obj_type :a) = "string) +end + +to mal_symbol :a +output symbol_new obj_val :a +end + +to mal_symbol_q :a +output bool_to_mal ((obj_type :a) = "symbol) +end + +to mal_keyword :a +output obj_new "keyword obj_val :a +end + +to mal_keyword_q :a +output bool_to_mal ((obj_type :a) = "keyword) +end + +to mal_pr_str [:args] +output obj_new "string pr_seq :args "true " " :space_char +end + +to mal_str [:args] +output obj_new "string pr_seq :args "false " " " +end + +to mal_prn [:args] +print pr_seq :args "true " " :space_char +output nil_new +end + +to mal_println [:args] +print pr_seq :args "false " " :space_char +output nil_new +end + +to mal_read_string :str +output read_str obj_val :str +end + +to mal_readline :prompt +localmake "line readline obj_val :prompt +if :line=[] [output nil_new] +output obj_new "string :line +end + +to mal_slurp :str +openread obj_val :str +setread obj_val :str +localmake "content " +while [not eofp] [ + make "content word :content readchar +] +close obj_val :str +output obj_new "string :content +end + +to mal_lt :a :b +output bool_to_mal ((obj_val :a) < (obj_val :b)) +end + +to mal_lte :a :b +output bool_to_mal ((obj_val :a) <= (obj_val :b)) +end + +to mal_gt :a :b +output bool_to_mal ((obj_val :a) > (obj_val :b)) +end + +to mal_gte :a :b +output bool_to_mal ((obj_val :a) >= (obj_val :b)) +end + +to mal_add :a :b +output obj_new "number ((obj_val :a) + (obj_val :b)) +end + +to mal_sub :a :b +output obj_new "number ((obj_val :a) - (obj_val :b)) +end + +to mal_mul :a :b +output obj_new "number ((obj_val :a) * (obj_val :b)) +end + +to mal_div :a :b +output obj_new "number ((obj_val :a) / (obj_val :b)) +end + +to mal_time_ms +; Native function timems is added to coms.c (see Dockerfile) +output obj_new "number timems +end + +to mal_list [:args] +output obj_new "list :args +end + +to mal_list_q :a +output bool_to_mal ((obj_type :a) = "list) +end + +to mal_vector [:args] +output obj_new "vector :args +end + +to mal_vector_q :a +output bool_to_mal ((obj_type :a) = "vector) +end + +to mal_hash_map [:args] +localmake "h [] +localmake "i 1 +while [:i < count :args] [ + make "h hashmap_put :h item :i :args item (:i + 1) :args + make "i (:i + 2) +] +output obj_new "hashmap :h +end + +to mal_map_q :a +output bool_to_mal ((obj_type :a) = "hashmap) +end + +to mal_assoc :map [:args] +localmake "h obj_val :map +localmake "i 1 +while [:i < count :args] [ + make "h hashmap_put :h item :i :args item (:i + 1) :args + make "i (:i + 2) +] +output obj_new "hashmap :h +end + +to mal_dissoc :map [:args] +localmake "h obj_val :map +foreach :args [make "h hashmap_delete :h ?] +output obj_new "hashmap :h +end + +to mal_get :map :key +localmake "val hashmap_get obj_val :map :key +if emptyp :val [output nil_new] +output :val +end + +to mal_contains_q :map :key +localmake "val hashmap_get obj_val :map :key +output bool_to_mal not emptyp :val +end + +to mal_keys :map +localmake "h obj_val :map +localmake "keys [] +localmake "i 1 +while [:i <= count :h] [ + make "keys lput item :i :h :keys + make "i (:i + 2) +] +output obj_new "list :keys +end + +to mal_vals :map +localmake "h obj_val :map +localmake "values [] +localmake "i 2 +while [:i <= count :h] [ + make "values lput item :i :h :values + make "i (:i + 2) +] +output obj_new "list :values +end + +to mal_sequential_q :a +output bool_to_mal sequentialp :a +end + +to mal_cons :a :b +output obj_new "list fput :a obj_val :b +end + +to mal_concat [:args] +output obj_new "list apply "sentence map [obj_val ?] :args +end + +to mal_nth :a :i +if (obj_val :i) >= _count :a [(throw "error [nth: index out of range])] +output nth :a obj_val :i +end + +to mal_first :a +output cond [ + [[(obj_type :a) = "nil] nil_new] + [[(_count :a) = 0] nil_new] + [else first obj_val :a] +] +end + +to mal_rest :a +output obj_new "list cond [ + [[(obj_type :a) = "nil] []] + [[(_count :a) = 0] []] + [else butfirst obj_val :a] +] +end + +to mal_empty_q :a +output bool_to_mal (emptyp obj_val :a) +end + +to mal_count :a +output obj_new "number _count :a +end + +to mal_apply :f [:args] +localmake "callargs obj_new "list sentence butlast :args obj_val last :args +output invoke_fn :f :callargs +end + +to mal_map :f :seq +output obj_new "list map [invoke_fn :f obj_new "list (list ?)] obj_val :seq +end + +to mal_conj :a0 [:rest] +case obj_type :a0 [ + [[list] localmake "newlist :a0 + foreach :rest [make "newlist mal_cons ? :newlist] + output :newlist ] + [[vector] output obj_new "vector sentence obj_val :a0 :rest ] + [else (throw "error [conj requires list or vector]) ] +] +end + +to mal_seq :a +case obj_type :a [ + [[string] + if (_count :a) = 0 [output nil_new] + localmake "chars [] + foreach obj_val :a [ make "chars lput obj_new "string ? :chars ] + output obj_new "list :chars ] + [[list] + if (_count :a) = 0 [output nil_new] + output :a ] + [[vector] + if (_count :a) = 0 [output nil_new] + output obj_new "list obj_val :a ] + [[nil] output nil_new ] + [else (throw "error [seq requires string or list or vector or nil]) ] +] +end + +to mal_meta :a +localmake "m obj_meta :a +if emptyp :m [output nil_new] +output :m +end + +to mal_with_meta :a :new_meta +localmake "m ifelse (obj_type :new_meta) = "nil [[]] [:new_meta] +output obj_new_with_meta obj_type :a obj_val :a :m +end + +to mal_atom :a +output obj_new "atom :a +end + +to mal_atom_q :a +output bool_to_mal ((obj_type :a) = "atom) +end + +to mal_deref :a +output obj_val :a +end + +to mal_reset_bang :a :val +.setfirst butfirst :a :val +output :val +end + +to invoke_fn :f :callargs +output case obj_type :f [ + [[nativefn] + apply obj_val :f obj_val :callargs ] + [[fn] + _eval fn_body :f env_new fn_env :f fn_args :f :callargs ] + [else + (throw "error [Wrong type for apply])] +] +end + +to mal_swap_bang :atom :f [:args] +localmake "callargs obj_new "list fput mal_deref :atom :args +output mal_reset_bang :atom invoke_fn :f :callargs +end + +to logo_to_mal :a +output cond [ + [[:a = "true] true_new] + [[:a = "false] false_new] + [[numberp :a] obj_new "number :a] + [[wordp :a] obj_new "string :a] + [[listp :a] obj_new "list map [logo_to_mal ?] :a] + [else nil_new] +] +end + +to mal_logo_eval :str +make "res runresult obj_val :str +if emptyp :res [output nil_new] +output logo_to_mal first :res +end + +make "core_ns [ + [[symbol =] [nativefn mal_equal_q]] + [[symbol throw] [nativefn mal_throw]] + + [[symbol nil?] [nativefn mal_nil_q]] + [[symbol true?] [nativefn mal_true_q]] + [[symbol false?] [nativefn mal_false_q]] + [[symbol string?] [nativefn mal_string_q]] + [[symbol symbol] [nativefn mal_symbol]] + [[symbol symbol?] [nativefn mal_symbol_q]] + [[symbol keyword] [nativefn mal_keyword]] + [[symbol keyword?] [nativefn mal_keyword_q]] + + [[symbol pr-str] [nativefn mal_pr_str]] + [[symbol str] [nativefn mal_str]] + [[symbol prn] [nativefn mal_prn]] + [[symbol println] [nativefn mal_println]] + [[symbol read-string] [nativefn mal_read_string]] + [[symbol readline] [nativefn mal_readline]] + [[symbol slurp] [nativefn mal_slurp]] + + [[symbol <] [nativefn mal_lt]] + [[symbol <=] [nativefn mal_lte]] + [[symbol >] [nativefn mal_gt]] + [[symbol >=] [nativefn mal_gte]] + [[symbol +] [nativefn mal_add]] + [[symbol -] [nativefn mal_sub]] + [[symbol *] [nativefn mal_mul]] + [[symbol /] [nativefn mal_div]] + [[symbol time-ms] [nativefn mal_time_ms]] + + [[symbol list] [nativefn mal_list]] + [[symbol list?] [nativefn mal_list_q]] + [[symbol vector] [nativefn mal_vector]] + [[symbol vector?] [nativefn mal_vector_q]] + [[symbol hash-map] [nativefn mal_hash_map]] + [[symbol map?] [nativefn mal_map_q]] + [[symbol assoc] [nativefn mal_assoc]] + [[symbol dissoc] [nativefn mal_dissoc]] + [[symbol get] [nativefn mal_get]] + [[symbol contains?] [nativefn mal_contains_q]] + [[symbol keys] [nativefn mal_keys]] + [[symbol vals] [nativefn mal_vals]] + + [[symbol sequential?] [nativefn mal_sequential_q]] + [[symbol cons] [nativefn mal_cons]] + [[symbol concat] [nativefn mal_concat]] + [[symbol nth] [nativefn mal_nth]] + [[symbol first] [nativefn mal_first]] + [[symbol rest] [nativefn mal_rest]] + [[symbol empty?] [nativefn mal_empty_q]] + [[symbol count] [nativefn mal_count]] + [[symbol apply] [nativefn mal_apply]] + [[symbol map] [nativefn mal_map]] + + [[symbol conj] [nativefn mal_conj]] + [[symbol seq] [nativefn mal_seq]] + + [[symbol meta] [nativefn mal_meta]] + [[symbol with-meta] [nativefn mal_with_meta]] + [[symbol atom] [nativefn mal_atom]] + [[symbol atom?] [nativefn mal_atom_q]] + [[symbol deref] [nativefn mal_deref]] + [[symbol reset!] [nativefn mal_reset_bang]] + [[symbol swap!] [nativefn mal_swap_bang]] + + [[symbol logo-eval] [nativefn mal_logo_eval]] +] diff --git a/logo/env.lg b/logo/env.lg new file mode 100644 index 0000000000..b3f5b74e89 --- /dev/null +++ b/logo/env.lg @@ -0,0 +1,51 @@ +load "../logo/printer.lg +load "../logo/types.lg + +to env_new :outer :binds :exprs +localmake "data [] +if not emptyp :binds [ + localmake "i 0 + while [:i < _count :binds] [ + ifelse (nth :binds :i) = [symbol &] [ + localmake "val drop :exprs :i + make "i (:i + 1) + localmake "key nth :binds :i + ] [ + localmake "val nth :exprs :i + localmake "key nth :binds :i + ] + make "data hashmap_put :data :key :val + make "i (:i + 1) + ] +] +output listtoarray list :outer :data +end + +to env_outer :env +output item 1 :env +end + +to env_data :env +output item 2 :env +end + +to env_find :env :key +if emptyp :env [output []] +localmake "val hashmap_get env_data :env :key +ifelse emptyp :val [ + output env_find env_outer :env :key +] [ + output :env +] +end + +to env_get :env :key +localmake "foundenv env_find :env :key +if emptyp :foundenv [(throw "error sentence (word "' pr_str :key "true "' ) [not found])] +output hashmap_get env_data :foundenv :key +end + +to env_set :env :key :val +.setitem 2 :env hashmap_put env_data :env :key :val +output :val +end diff --git a/logo/examples/tree.mal b/logo/examples/tree.mal new file mode 100644 index 0000000000..5813ad3257 --- /dev/null +++ b/logo/examples/tree.mal @@ -0,0 +1,25 @@ +; Draw a tree +; +; The classic Logo demo for recursive functions - now in Mal! + +; White background with blue pen +(logo-eval "setbackground 7") +(logo-eval "setpencolor 1") + +; Initialize turtle location +(logo-eval "penup setxy 0 -100 pendown") + +; Expose Logo drawing functions to Mal code +(def! fd (fn* [size] (logo-eval (str "fd " size)))) +(def! bk (fn* [size] (logo-eval (str "bk " size)))) +(def! lt (fn* [size] (logo-eval (str "lt " size)))) +(def! rt (fn* [size] (logo-eval (str "rt " size)))) + +; Tree parts +(def! leaf (fn* [size] (do (fd size) (bk size)))) +(def! branch (fn* [size] (do (fd size) (draw-tree size) (bk size)))) +(def! two-branches (fn* [size] (do (lt 10) (branch size) (rt 40) (branch size) (lt 30)))) +(def! draw-tree (fn* [size] (if (< size 5) (leaf size) (two-branches (/ size 2))))) + +; Draw it +(draw-tree 250) diff --git a/logo/printer.lg b/logo/printer.lg new file mode 100644 index 0000000000..efe1339854 --- /dev/null +++ b/logo/printer.lg @@ -0,0 +1,54 @@ +load "../logo/types.lg + +to pr_str :exp :readable +if emptyp :exp [output []] +output case obj_type :exp [ + [[nil] "nil] + [[true] "true] + [[false] "false] + [[number] obj_val :exp] + [[symbol] obj_val :exp] + [[keyword] word ": obj_val :exp] + [[string] print_string :exp :readable] + [[list] pr_seq obj_val :exp :readable "\( "\) :space_char] + [[vector] pr_seq obj_val :exp :readable "\[ "\] :space_char] + [[hashmap] pr_seq obj_val :exp :readable "\{ "\} :space_char] + [[atom] (word "\(atom :space_char pr_str obj_val :exp :readable "\) ) ] + [[nativefn] (word "#) ] + [[fn] (word "#) ] + [else (throw "error (sentence [unknown type] obj_type :exp))] +] +end + +to escape_string :s +localmake "i 1 +localmake "res " +while [:i <= count :s] [ + localmake "c item :i :s + make "res word :res cond [ + [[ :c = "\\ ] "\\\\ ] + [[ :c = char 10 ] "\\n ] + [[ :c = "\" ] "\\\" ] + [else :c ] + ] + make "i (:i + 1) +] +output :res +end + +to print_string :exp :readable +ifelse :readable [ + output (word "\" escape_string obj_val :exp "\" ) +] [ + output obj_val :exp +] +end + +to pr_seq :seq :readable :start_char :end_char :delim_char +localmake "res :start_char +foreach :seq [ + if # > 1 [make "res word :res :delim_char] + make "res word :res pr_str ? :readable +] +output word :res :end_char +end diff --git a/logo/reader.lg b/logo/reader.lg new file mode 100644 index 0000000000..dc4c85171e --- /dev/null +++ b/logo/reader.lg @@ -0,0 +1,221 @@ +load "../logo/types.lg + +make "open_paren_char char 40 +make "close_paren_char char 41 +make "open_bracket_char char 91 +make "close_bracket_char char 93 +make "open_brace_char char 123 +make "close_brace_char char 125 + +to newlinep :char +output case ascii :char [ + [[10 13] "true] + [else "false] +] +end + +to whitespacep :char +output case ascii :char [ + [[9 10 13 32] "true] + [else "false] +] +end + +to singlechartokenp :char +output case :char [ + [[ ( ) \[ \] \{ \} ' ` \^ @ ] "true] + [else "false] +] +end + +to separatorp :char +output ifelse whitespacep :char [ + "true +] [ + case :char [ + [[ ( ) \[ \] \{ \} ' \" ` , \; ] "true] + [else "false] + ] +] +end + +to read_comment_token :s +localmake "rest :s +while [not emptyp :rest] [ + localmake "c first :rest + ifelse newlinep :c [ + output list " butfirst :rest + ] [ + make "rest butfirst :rest + ] +] +output list " :rest +end + +to read_word_token :s +localmake "w " +localmake "rest :s +while [not emptyp :rest] [ + localmake "c first :rest + ifelse separatorp :c [ + output list :w :rest + ] [ + make "w word :w :c + make "rest butfirst :rest + ] +] +output list :w :rest +end + +to read_string_token :s +localmake "w first :s +localmake "rest butfirst :s +while [not emptyp :rest] [ + localmake "c first :rest + if :c = "" [ + make "w word :w :c + output list :w butfirst :rest + ] + if :c = "\\ [ + make "w word :w :c + make "rest butfirst :rest + make "c first :rest + ] + make "w word :w :c + make "rest butfirst :rest +] +(throw "error [Expected closing quotes]) +end + +to read_next_token :s +localmake "c first :s +localmake "rest butfirst :s +output cond [ + [[whitespacep :c] list " :rest] + [[:c = ",] list " :rest] + [[:c = "~] ifelse ((first :rest) = "@) [list "~@ butfirst :rest] [list "~ :rest] ] + [[singlechartokenp :c] list :c :rest] + [[:c = "\;] read_comment_token :s] + [[:c = ""] read_string_token :s] + [else read_word_token :s] +] +output list first :s butfirst :s +end + +to tokenize :str +localmake "tokens [] +localmake "s :str +while [not emptyp :s] [ + localmake "res read_next_token :s + localmake "token first :res + make "s last :res + if not emptyp :token [ + make "tokens lput :token :tokens + ] +] +output :tokens +end + +to reader_new :tokens +output listtoarray list :tokens 1 +end + +to reader_peek :reader +localmake "tokens item 1 :reader +localmake "pos item 2 :reader +if :pos > count :tokens [output []] +output item :pos :tokens +end + +to reader_next :reader +make "token reader_peek :reader +localmake "pos item 2 :reader +setitem 2 :reader (1 + :pos) +output :token +end + +to unescape_string :token +localmake "s butfirst butlast :token ; remove surrounding double-quotes +localmake "i 1 +localmake "res " +while [:i <= count :s] [ + localmake "c item :i :s + ifelse :c = "\\ [ + make "i (:i + 1) + make "c item :i :s + make "res word :res case :c [ + [[ n ] char 10] + [[ " ] "\" ] + [[ \\ ] "\\ ] + [else :c] + ] + ] [ + make "res word :res :c + ] + make "i (:i + 1) +] +output :res +end + +to read_atom :reader +localmake "token reader_next :reader +output cond [ + [[:token = "nil] nil_new] + [[:token = "true] true_new] + [[:token = "false] false_new] + [[numberp :token] obj_new "number :token] + [[(first :token) = ": ] obj_new "keyword butfirst :token] + [[(first :token) = "\" ] obj_new "string unescape_string :token] + [else symbol_new :token] +] +end + +to read_seq :reader :value_type :start_char :end_char +localmake "token reader_next :reader +if :token <> :start_char [(throw "error sentence "expected (word "' :start_char "'))] +localmake "seq [] +make "token reader_peek :reader +while [:token <> :end_char] [ + if emptyp :token [(throw "error (sentence [expected] (word "' :end_char "',) [got EOF]))] + make "seq lput read_form :reader :seq + make "token reader_peek :reader +] +ignore reader_next :reader +output obj_new :value_type :seq +end + +to reader_macro :reader :symbol_name +ignore reader_next :reader +output obj_new "list list symbol_new :symbol_name read_form :reader +end + +to with_meta_reader_macro :reader +ignore reader_next :reader +localmake "meta read_form :reader +output obj_new "list (list symbol_new "with-meta read_form :reader :meta) +end + +to read_form :reader +output case reader_peek :reader [ + [[ ' ] reader_macro :reader "quote ] + [[ ` ] reader_macro :reader "quasiquote ] + [[ ~ ] reader_macro :reader "unquote ] + [[ ~@ ] reader_macro :reader "splice-unquote ] + [[ \^ ] with_meta_reader_macro :reader ] + [[ @ ] reader_macro :reader "deref ] + [[ ( ] read_seq :reader "list :open_paren_char :close_paren_char ] + [[ ) ] (throw "error sentence [unexpected] (word "' :close_paren_char "')) ] + [[ \[ ] read_seq :reader "vector :open_bracket_char :close_bracket_char ] + [[ \] ] (throw "error sentence [unexpected] (word "' :close_bracket_char "')) ] + [[ \{ ] read_seq :reader "hashmap :open_brace_char :close_brace_char ] + [[ \} ] (throw "error sentence [unexpected] (word "' :close_brace_char "')) ] + [else read_atom :reader] +] +end + +to read_str :str +localmake "tokens tokenize :str +if emptyp :tokens [output []] +localmake "reader reader_new :tokens +output read_form :reader +end diff --git a/logo/readline.lg b/logo/readline.lg new file mode 100644 index 0000000000..b015ff397f --- /dev/null +++ b/logo/readline.lg @@ -0,0 +1,27 @@ +make "backspace_char char 8 +make "space_char char 32 + +to readline :prompt +type :prompt +wait 0 ; flush standard output +localmake "line " +forever [ + localmake "c readchar + ifelse emptyp :c [ + output [] + ] [ + localmake "ascii rawascii :c + case :ascii [ + [[4] output []] + [[10] type :c + output :line] + [[127] if not emptyp :line [ + type (word :backspace_char :space_char :backspace_char) + make "line butlast :line + ]] + [else type :c + make "line word :line :c] + ] + ] +] +end diff --git a/logo/run b/logo/run new file mode 100755 index 0000000000..5d90e8a7ab --- /dev/null +++ b/logo/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec logo $(dirname $0)/${STEP:-stepA_mal}.lg - "${@}" diff --git a/logo/step0_repl.lg b/logo/step0_repl.lg new file mode 100644 index 0000000000..f62cd8d675 --- /dev/null +++ b/logo/step0_repl.lg @@ -0,0 +1,31 @@ +load "../logo/readline.lg + +to _read :str +output :str +end + +to _eval :ast :env +output :ast +end + +to _print :exp +output :exp +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + print _print _eval _read :line [] + ] + ] +] +end + +repl +bye diff --git a/logo/step1_read_print.lg b/logo/step1_read_print.lg new file mode 100644 index 0000000000..c3e5e61008 --- /dev/null +++ b/logo/step1_read_print.lg @@ -0,0 +1,41 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg + +to _read :str +output read_str :str +end + +to _eval :ast :env +output :ast +end + +to _print :exp +output pr_str :exp "true +end + +to rep :str +output _print _eval _read :str [] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +repl +bye diff --git a/logo/step2_eval.lg b/logo/step2_eval.lg new file mode 100644 index 0000000000..de1be205a2 --- /dev/null +++ b/logo/step2_eval.lg @@ -0,0 +1,78 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] localmake "val hashmap_get :env :ast + if emptyp :val [(throw "error sentence (word "' obj_val :ast "' ) [not found])] + :val ] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :ast :env +if (obj_type :ast) <> "list [output eval_ast :ast :env] +if emptyp obj_val :ast [output :ast] +make "el obj_val eval_ast :ast :env +output apply first :el butfirst :el +end + +to _print :exp +output pr_str :exp "true +end + +to rep :str +output _print _eval _read :str :repl_env +end + +to mal_add :a :b +output obj_new "number ((obj_val :a) + (obj_val :b)) +end + +to mal_sub :a :b +output obj_new "number ((obj_val :a) - (obj_val :b)) +end + +to mal_mul :a :b +output obj_new "number ((obj_val :a) * (obj_val :b)) +end + +to mal_div :a :b +output obj_new "number ((obj_val :a) / (obj_val :b)) +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env [] +make "repl_env hashmap_put :repl_env symbol_new "+ "mal_add +make "repl_env hashmap_put :repl_env symbol_new "- "mal_sub +make "repl_env hashmap_put :repl_env symbol_new "* "mal_mul +make "repl_env hashmap_put :repl_env symbol_new "/ "mal_div +repl +bye diff --git a/logo/step3_env.lg b/logo/step3_env.lg new file mode 100644 index 0000000000..05147038d6 --- /dev/null +++ b/logo/step3_env.lg @@ -0,0 +1,96 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :ast :env +if (obj_type :ast) <> "list [output eval_ast :ast :env] +if emptyp obj_val :ast [output :ast] +localmake "a0 nth :ast 0 +case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + output _eval nth :ast 2 :letenv ] + + [else + make "el obj_val eval_ast :ast :env + output apply first :el butfirst :el ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to rep :str +output _print _eval _read :str :repl_env +end + +to mal_add :a :b +output obj_new "number ((obj_val :a) + (obj_val :b)) +end + +to mal_sub :a :b +output obj_new "number ((obj_val :a) - (obj_val :b)) +end + +to mal_mul :a :b +output obj_new "number ((obj_val :a) * (obj_val :b)) +end + +to mal_div :a :b +output obj_new "number ((obj_val :a) / (obj_val :b)) +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env env_new [] [] [] +ignore env_set :repl_env obj_new "symbol "+ "mal_add +ignore env_set :repl_env obj_new "symbol "- "mal_sub +ignore env_set :repl_env obj_new "symbol "* "mal_mul +ignore env_set :repl_env obj_new "symbol "/ "mal_div +repl +bye diff --git a/logo/step4_if_fn_do.lg b/logo/step4_if_fn_do.lg new file mode 100644 index 0000000000..fd1293ea3d --- /dev/null +++ b/logo/step4_if_fn_do.lg @@ -0,0 +1,113 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :ast :env +if (obj_type :ast) <> "list [output eval_ast :ast :env] +if emptyp obj_val :ast [output :ast] +localmake "a0 nth :ast 0 +case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + output _eval nth :ast 2 :letenv ] + + [[[symbol do]] + output last obj_val eval_ast rest :ast :env ] + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + output case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + _eval nth :ast 3 :env + ] [ + nil_new + ]] + [else _eval nth :ast 2 :env] + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + localmake "funcenv env_new fn_env :f fn_args :f rest :el + output _eval fn_body :f :funcenv ] + [else + (throw "error [Wrong type for apply])] + ] ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +repl +bye diff --git a/logo/step5_tco.lg b/logo/step5_tco.lg new file mode 100644 index 0000000000..a9171f39a2 --- /dev/null +++ b/logo/step5_tco.lg @@ -0,0 +1,123 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +repl +bye diff --git a/logo/step6_file.lg b/logo/step6_file.lg new file mode 100644 index 0000000000..2bf753ea91 --- /dev/null +++ b/logo/step6_file.lg @@ -0,0 +1,151 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/step7_quote.lg b/logo/step7_quote.lg new file mode 100644 index 0000000000..5a29b7f535 --- /dev/null +++ b/logo/step7_quote.lg @@ -0,0 +1,178 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/step8_macros.lg b/logo/step8_macros.lg new file mode 100644 index 0000000000..885eff3fbd --- /dev/null +++ b/logo/step8_macros.lg @@ -0,0 +1,213 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to macrocallp :ast :env +if (obj_type :ast) = "list [ + if (_count :ast) > 0 [ + localmake "a0 nth :ast 0 + if (obj_type :a0) = "symbol [ + if not emptyp env_find :env :a0 [ + localmake "f env_get :env :a0 + if (obj_type :f) = "fn [ + output fn_is_macro :f + ] + ] + ] + ] +] +output "false +end + +to _macroexpand :ast :env +if not macrocallp :ast :env [output :ast] +localmake "a0 nth :ast 0 +localmake "f env_get :env :a0 +output _macroexpand invoke_fn :f rest :ast :env +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + make "ast _macroexpand :ast :env + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol defmacro!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + localmake "macro_fn _eval :a2 :env + fn_set_macro :macro_fn + output env_set :env :a1 :macro_fn ] + + [[[symbol macroexpand]] + output _macroexpand nth :ast 1 :env ] + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| +ignore re "|(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))| +ignore re "|(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/step9_try.lg b/logo/step9_try.lg new file mode 100644 index 0000000000..7639ff47c1 --- /dev/null +++ b/logo/step9_try.lg @@ -0,0 +1,228 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to macrocallp :ast :env +if (obj_type :ast) = "list [ + if (_count :ast) > 0 [ + localmake "a0 nth :ast 0 + if (obj_type :a0) = "symbol [ + if not emptyp env_find :env :a0 [ + localmake "f env_get :env :a0 + if (obj_type :f) = "fn [ + output fn_is_macro :f + ] + ] + ] + ] +] +output "false +end + +to _macroexpand :ast :env +if not macrocallp :ast :env [output :ast] +localmake "a0 nth :ast 0 +localmake "f env_get :env :a0 +output _macroexpand invoke_fn :f rest :ast :env +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + make "ast _macroexpand :ast :env + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol defmacro!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + localmake "macro_fn _eval :a2 :env + fn_set_macro :macro_fn + output env_set :env :a1 :macro_fn ] + + [[[symbol macroexpand]] + output _macroexpand nth :ast 1 :env ] + + [[[symbol try*]] + localmake "result nil_new + catch "error [make "result _eval nth :ast 1 :env] + localmake "exception error + ifelse or emptyp :exception ((_count :ast) < 3) [ + output :result + ] [ + localmake "e first butfirst :exception + localmake "exception_obj ifelse :e = "_mal_exception_ [:global_exception] [obj_new "string :e] + localmake "a2 nth :ast 2 + localmake "catchenv env_new :env [] [] + ignore env_set :catchenv nth :a2 1 :exception_obj + output _eval nth :a2 2 :catchenv + ] ] + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| +ignore re "|(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))| +ignore re "|(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/stepA_mal.lg b/logo/stepA_mal.lg new file mode 100644 index 0000000000..25db73000b --- /dev/null +++ b/logo/stepA_mal.lg @@ -0,0 +1,232 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to macrocallp :ast :env +if (obj_type :ast) = "list [ + if (_count :ast) > 0 [ + localmake "a0 nth :ast 0 + if (obj_type :a0) = "symbol [ + if not emptyp env_find :env :a0 [ + localmake "f env_get :env :a0 + if (obj_type :f) = "fn [ + output fn_is_macro :f + ] + ] + ] + ] +] +output "false +end + +to _macroexpand :ast :env +if not macrocallp :ast :env [output :ast] +localmake "a0 nth :ast 0 +localmake "f env_get :env :a0 +output _macroexpand invoke_fn :f rest :ast :env +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + make "ast _macroexpand :ast :env + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol defmacro!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + localmake "macro_fn _eval :a2 :env + fn_set_macro :macro_fn + output env_set :env :a1 :macro_fn ] + + [[[symbol macroexpand]] + output _macroexpand nth :ast 1 :env ] + + [[[symbol try*]] + localmake "result nil_new + catch "error [make "result _eval nth :ast 1 :env] + localmake "exception error + ifelse or emptyp :exception ((_count :ast) < 3) [ + output :result + ] [ + localmake "e first butfirst :exception + localmake "exception_obj ifelse :e = "_mal_exception_ [:global_exception] [obj_new "string :e] + localmake "a2 nth :ast 2 + localmake "catchenv env_new :env [] [] + ignore env_set :catchenv nth :a2 1 :exception_obj + output _eval nth :a2 2 :catchenv + ] ] + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! *host-language* "logo")| +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| +ignore re "|(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))| +ignore re "|(def! *gensym-counter* (atom 0))| +ignore re "|(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))| +ignore re "|(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)))))))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +ignore re "|(println (str "Mal [" *host-language* "]"))| +repl +bye diff --git a/logo/tests/stepA_mal.mal b/logo/tests/stepA_mal.mal new file mode 100644 index 0000000000..904b7db3bf --- /dev/null +++ b/logo/tests/stepA_mal.mal @@ -0,0 +1,30 @@ +;; Testing basic Logo interop + +(logo-eval "7") +;=>7 + +(logo-eval "\"hello") +;=>"hello" + +(logo-eval "[7 8 9]") +;=>(7 8 9) + +(logo-eval "123 = 123") +;=>true + +(logo-eval "not emptyp []") +;=>false + +(logo-eval "print [hello world]") +; hello world +;=>nil + +(logo-eval "make \"foo 8") +(logo-eval ":foo") +;=>8 + +(logo-eval "apply \"word map [reverse ?] [Abc Abcd Abcde]") +;=>"cbAdcbAedcbA" + +(logo-eval "map [1 + ?] [1 2 3]") +;=>(2 3 4) diff --git a/logo/types.lg b/logo/types.lg new file mode 100644 index 0000000000..e7bffd16ac --- /dev/null +++ b/logo/types.lg @@ -0,0 +1,175 @@ +; Make Logo's string-comparison case sensitive +make "caseignoredp "false + +; Load the 'case' library macro +case "dummy [] + +; Redefine 'case' macro to not override caseignoredp +.macro case :case.value :case.clauses +catch "case.error [output case.helper :case.value :case.clauses] +(throw "error [Empty CASE clause]) +end + +to obj_new :type :val +output list :type :val +end + +to obj_new_with_meta :type :val :meta +output (list :type :val :meta) +end + +to obj_type :obj +output first :obj +end + +to obj_val :obj +output item 2 :obj +end + +to obj_meta :obj +if (count :obj) < 3 [output []] +output item 3 :obj +end + +make "global_nil obj_new "nil [] + +to nil_new +output :global_nil +end + +make "global_true obj_new "true [] + +to true_new +output :global_true +end + +make "global_false obj_new "false [] + +to false_new +output :global_false +end + +to symbol_new :name +output obj_new "symbol :name +end + +to hashmap_get :h :key +localmake "i 1 +while [:i < count :h] [ + if equal_q item :i :h :key [ + output item (:i + 1) :h + ] + make "i (:i + 2) +] +output [] +end + +; Returns a new list with the key-val pair set +to hashmap_put :h :key :val +localmake "res hashmap_delete :h :key +make "res lput :key :res +make "res lput :val :res +output :res +end + +; Returns a new list without the key-val pair set +to hashmap_delete :h :key +localmake "res [] +localmake "i 1 +while [:i < count :h] [ + if (item :i :h) <> :key [ + make "res lput item :i :h :res + make "res lput item (:i + 1) :h :res + ] + make "i (:i + 2) +] +output :res +end + +to fn_new :args :env :body +output obj_new "fn (list :args :env :body "false) +end + +to fn_args :fn +output item 1 obj_val :fn +end + +to fn_env :fn +output item 2 obj_val :fn +end + +to fn_body :fn +output item 3 obj_val :fn +end + +to fn_is_macro :fn +output item 4 obj_val :fn +end + +to fn_set_macro :fn +.setfirst butfirst butfirst butfirst obj_val :fn "true +end + +; zero-based sequence addressing +to nth :seq :index +output item (:index + 1) obj_val :seq +end + +to _count :seq +output count obj_val :seq +end + +to rest :seq +output obj_new obj_type :seq butfirst obj_val :seq +end + +to drop :seq :num +if or :num = 0 (_count :seq) = 0 [output :seq] +foreach obj_val :seq [ + if # >= :num [output obj_new obj_type :seq ?rest] +] +end + +to sequentialp :obj +output or ((obj_type :obj) = "list) ((obj_type :obj) = "vector) +end + +to equal_sequential_q :a :b +if (_count :a) <> (_count :b) [output "false] +(foreach obj_val :a obj_val :b [ + if not equal_q ?1 ?2 [output "false] +]) +output "true +end + +to equal_hashmap_q :a :b +if (_count :a) <> (_count :b) [output "false] +localmake "a_keys obj_val mal_keys :a +foreach :a_keys [ + localmake "a_val hashmap_get obj_val :a ? + localmake "b_val hashmap_get obj_val :b ? + if emptyp :b_val [output "false] + if not equal_q :a_val :b_val [output "false] +] +output "true +end + +to equal_q :a :b +output cond [ + [[and sequentialp :a sequentialp :b] + equal_sequential_q :a :b] + [[((obj_type :a) = (obj_type :b))] + case obj_type :a [ + [[true false nil] "true] + [[number string keyword symbol] ((obj_val :a) = (obj_val :b))] + [[hashmap] equal_hashmap_q :a :b] + [[atom] equal_q obj_val :a obj_val :b] + [else "false] + ]] + [else "false] +] +end + +to symbolnamedp :name :obj +output and ((obj_type :obj) = "symbol) ((obj_val :obj) = :name) +end