diff options
| author | Joel Martin <github@martintribe.org> | 2014-11-08 16:56:36 -0600 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2015-01-09 16:16:45 -0600 |
| commit | 891c3f3b478292ad0bfca44b0dc098a2aecc9a5d (patch) | |
| tree | d30c7923ffee7699cfca94af84f3be297448fff2 | |
| parent | 9b3362e86a57ed7f14c5fd018c37713185e0c154 (diff) | |
| download | mal-891c3f3b478292ad0bfca44b0dc098a2aecc9a5d.tar.gz mal-891c3f3b478292ad0bfca44b0dc098a2aecc9a5d.zip | |
CoffeeScript: add all steps. Self-hosting.
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | README.md | 11 | ||||
| -rw-r--r-- | coffee/Makefile | 15 | ||||
| -rw-r--r-- | coffee/core.coffee | 86 | ||||
| -rw-r--r-- | coffee/env.coffee | 24 | ||||
| -rw-r--r-- | coffee/package.json | 9 | ||||
| -rw-r--r-- | coffee/printer.coffee | 24 | ||||
| -rw-r--r-- | coffee/reader.coffee | 88 | ||||
| -rw-r--r-- | coffee/step0_repl.coffee | 20 | ||||
| -rw-r--r-- | coffee/step1_read_print.coffee | 27 | ||||
| -rw-r--r-- | coffee/step2_eval.coffee | 52 | ||||
| -rw-r--r-- | coffee/step3_env.coffee | 63 | ||||
| -rw-r--r-- | coffee/step4_if_fn_do.coffee | 76 | ||||
| -rw-r--r-- | coffee/step5_tco.coffee | 82 | ||||
| -rw-r--r-- | coffee/step6_file.coffee | 90 | ||||
| -rw-r--r-- | coffee/step7_quote.coffee | 106 | ||||
| -rw-r--r-- | coffee/step8_macros.coffee | 126 | ||||
| -rw-r--r-- | coffee/step9_try.coffee | 136 | ||||
| -rw-r--r-- | coffee/stepA_interop.coffee | 142 | ||||
| -rw-r--r-- | coffee/tests/stepA_interop.mal | 24 | ||||
| -rw-r--r-- | coffee/types.coffee | 115 | ||||
| -rw-r--r-- | tests/step8_macros.mal | 13 |
23 files changed, 1331 insertions, 4 deletions
@@ -3,6 +3,7 @@ make/mal.mk js/node_modules js/mal.js js/mal_web.js +coffee/node_modules bash/mal.sh c/*.o *.pyc @@ -10,7 +10,8 @@ PYTHON = python # Settings # -IMPLS = bash c clojure cs go java js make mal perl php ps python r ruby rust +IMPLS = bash c clojure coffee cs go java js make mal perl php ps \ + python r ruby rust step0 = step0_repl step1 = step1_read_print @@ -50,6 +51,7 @@ STEP_TEST_FILES = $(strip $(wildcard $(1)/tests/$($(2)).mal) $(wildcard tests/$( bash_STEP_TO_PROG = bash/$($(1)).sh c_STEP_TO_PROG = c/$($(1)) clojure_STEP_TO_PROG = clojure/src/$($(1)).clj +coffee_STEP_TO_PROG = coffee/$($(1)).coffee cs_STEP_TO_PROG = cs/$($(1)).exe go_STEP_TO_PROG = go/$($(1)) java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java @@ -68,6 +70,7 @@ rust_STEP_TO_PROG = rust/target/$($(1)) bash_RUNSTEP = bash ../$(2) $(3) c_RUNSTEP = ../$(2) $(3) clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3) +coffee_RUNSTEP = coffee ../$(2) $(3) cs_RUNSTEP = mono ../$(2) --raw $(3) go_RUNSTEP = ../$(2) $(3) java_RUNSTEP = mvn -quiet exec:java -Dexec.mainClass="mal.$($(1))" -Dexec.args="--raw$(if $(3), $(3),)" @@ -3,12 +3,13 @@ ## Description Mal is an interpreter for a subset of the Clojure programming -language. Mal is implemented from scratch in 16 different languages: +language. Mal is implemented from scratch in 17 different languages: * Bash shell * C * C# * Clojure +* CoffeeScript * Go * Java * Javascript ([Online Demo](http://kanaka.github.io/mal)) @@ -88,6 +89,14 @@ cd clojure lein with-profile +stepX trampoline run ``` +### CoffeeScript + +``` +sudo npm install -g coffee-script +cd coffee +coffee ./stepX_YYY +``` + ### Go ``` diff --git a/coffee/Makefile b/coffee/Makefile new file mode 100644 index 0000000..d2212ed --- /dev/null +++ b/coffee/Makefile @@ -0,0 +1,15 @@ +TESTS = + +SOURCES_BASE = node_readline.coffee types.coffee \ + reader.coffee printer.coffee +SOURCES_LISP = env.coffee core.coffee stepA_interop.coffee +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +#all: mal.rb + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ +stats-lisp: $(SOURCES_LISP) + @wc $^ diff --git a/coffee/core.coffee b/coffee/core.coffee new file mode 100644 index 0000000..d01e76f --- /dev/null +++ b/coffee/core.coffee @@ -0,0 +1,86 @@ +readline = require "../js/node_readline" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +[_pr_str, println] = [printer._pr_str, printer.println] + +# Sequence functions +conj = (seq, args...) -> + switch types._obj_type(seq) + when 'list' + lst = types._clone(seq) + lst.unshift(x) for x in args + lst + when 'vector' + lst = types._clone(seq) + lst.push(args...) + types._vector(lst...) + else throw new Error "conj called on " + types._obj_type(seq) + +# Metadata functions +with_meta = (obj,m) -> + new_obj = types._clone(obj) + new_obj.__meta__ = m + new_obj + + +exports.ns = { + '=': (a,b) -> types._equal_Q(a,b), + 'throw': (a) -> throw a, + 'nil?': types._nil_Q, + 'true?': types._true_Q, + 'false?': types._false_Q, + 'symbol': types._symbol, + 'symbol?': types._symbol_Q, + + 'pr-str': (a...) -> a.map((exp) -> _pr_str(exp,true)).join(" "), + 'str': (a...) -> a.map((exp) -> _pr_str(exp,false)).join(""), + 'prn': (a...) -> println(a.map((exp) -> _pr_str(exp,true))...), + 'println': (a...) -> println(a.map((exp) -> _pr_str(exp,false))...), + 'readline': readline.readline, + 'read-string': reader.read_str, + 'slurp': (a) -> require('fs').readFileSync(a, 'utf-8'), + '<': (a,b) -> a<b, + '<=': (a,b) -> a<=b, + '>': (a,b) -> a>b, + '>=': (a,b) -> a>=b, + '+': (a,b) -> a+b, + '-': (a,b) -> a-b, + '*': (a,b) -> a*b, + '/': (a,b) -> a/b, + 'time-ms': () -> new Date().getTime(), + + 'list': (a...) -> a, + 'list?': types._list_Q, + 'vector': (a...) -> types._vector(a...), + 'vector?': types._vector_Q, + 'hash-map': (a...) -> types._hash_map(a...), + 'map?': types._hash_map_Q, + 'assoc': (a,b...) -> types._assoc_BANG(types._clone(a), b...), + 'dissoc': (a,b...) -> types._dissoc_BANG(types._clone(a), b...), + 'get': (a,b) -> if a != null and b of a then a[b] else null, + 'contains?': (a,b) -> b of a, + 'keys': (a) -> k for k of a, + 'vals': (a) -> v for k,v of a, + + 'sequential?': types._sequential_Q, + 'cons': (a,b) -> [a].concat(b), + 'concat': (a=[],b...) -> a.concat(b...), + 'nth': (a,b) -> if a.length > b then a[b] else null, + 'first': (a) -> if a.length > 0 then a[0] else null, + 'rest': (a) -> a[1..], + 'empty?': (a) -> a.length == 0, + 'count': (a) -> a.length, + 'apply': (a,b...) -> a(b[0..-2].concat(b[b.length-1])...), + 'map': (a,b) -> b.map((x) -> a(x)), + 'conj': conj, + + 'with-meta': with_meta, + 'meta': (a) -> a.__meta__ or null, + 'atom': types._atom, + 'atom?': types._atom_Q, + 'deref': (a) -> a.val, + 'reset!': (a,b) -> a.val = b, + 'swap!': (a,b,c...) -> a.val = b([a.val].concat(c)...), } + +# vim: ts=2:sw=2 diff --git a/coffee/env.coffee b/coffee/env.coffee new file mode 100644 index 0000000..667e467 --- /dev/null +++ b/coffee/env.coffee @@ -0,0 +1,24 @@ +types = require "./types.coffee" + +# Env +exports.Env = class Env + constructor: (@outer=null, @binds=[], @exprs=[]) -> + @data = {} + if @binds.length > 0 + for b,i in @binds + if types._symbol_Q(b) && b.name == "&" + @data[@binds[i+1].name] = exprs[i..] + break + else + @data[b.name] = exprs[i] + find: (key) -> + if key of @data then @ + else if @outer then @outer.find(key) + else null + set: (key, value) -> @data[key] = value + get: (key) -> + env = @find(key) + throw new Error("'" + key + "' not found") if !env + env.data[key] + +# vim: ts=2:sw=2 diff --git a/coffee/package.json b/coffee/package.json new file mode 100644 index 0000000..859ec4a --- /dev/null +++ b/coffee/package.json @@ -0,0 +1,9 @@ +{ + "name": "mal", + "version": "0.0.1", + "description": "Make a Lisp (mal) language implemented in CoffeeScript", + "dependencies": { + "ffi": "1.2.x", + "coffee-script": "~1.8" + } +} diff --git a/coffee/printer.coffee b/coffee/printer.coffee new file mode 100644 index 0000000..7844416 --- /dev/null +++ b/coffee/printer.coffee @@ -0,0 +1,24 @@ +types = require "./types.coffee" + +exports.println = (args...) -> console.log(args.join(" ")) || null + +exports._pr_str = _pr_str = (obj, print_readably=true) -> + _r = print_readably + switch types._obj_type obj + when 'list' then '(' + obj.map((e) -> _pr_str(e,_r)).join(' ') + ')' + when 'vector' then '[' + obj.map((e) -> _pr_str(e,_r)).join(' ') + ']' + when 'hash-map' + ret = [] + ret.push(_pr_str(k,_r), _pr_str(v,_r)) for k,v of obj + '{' + ret.join(' ') + '}' + when 'string' + if _r then '"' + (obj.replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n')) + '"' + else obj + when 'symbol' then obj.name + when 'nil' then 'nil' + when 'atom' then "(atom " + _pr_str(obj.val,_r) + ")" + else obj.toString() + +# vim: ts=2:sw=2 diff --git a/coffee/reader.coffee b/coffee/reader.coffee new file mode 100644 index 0000000..5882b43 --- /dev/null +++ b/coffee/reader.coffee @@ -0,0 +1,88 @@ +types = require "./types.coffee" +[_symbol, _vector, _hash_map] = [types._symbol, + types._vector, + types._hash_map] + + +class Reader + constructor: (@tokens) -> @position = 0 + next: -> @tokens[@position++] + peek: -> @tokens[@position] + skip: -> + @position++ + @ + +tokenize = (str) -> + re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g + results = [] + while (match = re.exec(str)[1]) != "" + continue if match[0] == ';' + results.push(match) + results + +read_atom = (rdr) -> + token = rdr.next() + if token.match /^-?[0-9]+$/ then parseInt token,10 + else if token.match /^-?[0-9][0-9.]*$/ then parseFloat token,10 + else if token[0] == '"' + token.slice(1, token.length-1) + .replace(/\\"/g, '"') + .replace(/\\n/g, "\n") + else if token == "nil" then null + else if token == "true" then true + else if token == "false" then false + else _symbol(token) + +read_list = (rdr, start='(', end=')') -> + ast = [] + token = rdr.next() + throw new Error "expected '" + start + "'" if token != start + while (token = rdr.peek()) != end + throw new Error "expected '" + end + "', got EOF" if !token + ast.push read_form rdr + rdr.next() + ast + +read_vector = (rdr) -> + _vector(read_list(rdr, '[', ']')...) + +read_hash_map = (rdr) -> + _hash_map(read_list(rdr, '{', '}')...) + +read_form = (rdr) -> + token = rdr.peek() + switch token + when '\'' then [_symbol('quote'), read_form(rdr.skip())] + when '`' then [_symbol('quasiquote'), read_form(rdr.skip())] + when '~' then [_symbol('unquote'), read_form(rdr.skip())] + when '~@' then [_symbol('splice-unquote'), read_form(rdr.skip())] + when '^' + meta = read_form(rdr.skip()) + [_symbol('with-meta'), read_form(rdr), meta] + when '@' then [_symbol('deref'), read_form(rdr.skip())] + + # list + when ')' then throw new Error "unexpected ')'" + when '(' then read_list(rdr) + # vector + when ']' then throw new Error "unexpected ']'" + when '[' then read_vector(rdr) + # hash-map + when '}' then throw new Error "unexpected '}'" + when '{' then read_hash_map(rdr) + # atom + else read_atom(rdr) + + +exports.BlankException = BlankException = (msg) -> null + +exports.read_str = read_str = (str) -> + tokens = tokenize(str) + throw new BlankException() if tokens.length == 0 + read_form(new Reader(tokens)) + +#console.log read_str "(1 \"two\" three)" +#console.log read_str "[1 2 3]" +#console.log read_str '{"abc" 123 "def" 456}' + +# vim: ts=2:sw=2 diff --git a/coffee/step0_repl.coffee b/coffee/step0_repl.coffee new file mode 100644 index 0000000..4fa9e40 --- /dev/null +++ b/coffee/step0_repl.coffee @@ -0,0 +1,20 @@ +readline = require "./node_readline.coffee" + +# read +READ = (str) -> str + +# eval +EVAL = (ast, env) -> ast + +# print +PRINT = (exp) -> exp + +# repl +rep = (str) -> PRINT(EVAL(READ(str), {})) + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + console.log rep line + +# vim: ts=2:sw=2 diff --git a/coffee/step1_read_print.coffee b/coffee/step1_read_print.coffee new file mode 100644 index 0000000..28edd1b --- /dev/null +++ b/coffee/step1_read_print.coffee @@ -0,0 +1,27 @@ +readline = require "./node_readline.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" + +# read +READ = (str) -> reader.read_str str + +# eval +EVAL = (ast, env) -> ast + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +rep = (str) -> PRINT(EVAL(READ(str), {})) + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step2_eval.coffee b/coffee/step2_eval.coffee new file mode 100644 index 0000000..1b3438e --- /dev/null +++ b/coffee/step2_eval.coffee @@ -0,0 +1,52 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" + +# read +READ = (str) -> reader.read_str str + +# eval +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env[ast.name] + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + [f, args...] = eval_ast ast, env + f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = {} +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +repl_env["+"] = (a,b) -> a+b +repl_env["-"] = (a,b) -> a-b +repl_env["*"] = (a,b) -> a*b +repl_env["/"] = (a,b) -> a/b + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step3_env.coffee b/coffee/step3_env.coffee new file mode 100644 index 0000000..a5398e7 --- /dev/null +++ b/coffee/step3_env.coffee @@ -0,0 +1,63 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env + +# read +READ = (str) -> reader.read_str str + +# eval +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + EVAL(a2, let_env) + else + [f, args...] = eval_ast ast, env + f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +repl_env.set "+", (a,b) -> a+b +repl_env.set "-", (a,b) -> a-b +repl_env.set "*", (a,b) -> a*b +repl_env.set "/", (a,b) -> a/b + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step4_if_fn_do.coffee b/coffee/step4_if_fn_do.coffee new file mode 100644 index 0000000..037b58a --- /dev/null +++ b/coffee/step4_if_fn_do.coffee @@ -0,0 +1,76 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + EVAL(a2, let_env) + when "do" + el = eval_ast(ast[1..], env) + el[el.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then EVAL(a3, env) else null + else + EVAL(a2, env) + when "fn*" + (args...) -> EVAL(a2, new Env(env, a1, args)) + else + [f, args...] = eval_ast ast, env + f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns + +# core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step5_tco.coffee b/coffee/step5_tco.coffee new file mode 100644 index 0000000..4003e79 --- /dev/null +++ b/coffee/step5_tco.coffee @@ -0,0 +1,82 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + loop + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + return env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + ast = a2 + env = let_env + when "do" + eval_ast(ast[1..-2], env) + ast = ast[ast.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then ast = a3 else return null + else + ast = a2 + when "fn*" + return types._function(EVAL, a2, env, a1) + else + [f, args...] = eval_ast ast, env + if types._function_Q(f) + ast = f.__ast__ + env = f.__gen_env__(args) + else + return f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns + +# core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step6_file.coffee b/coffee/step6_file.coffee new file mode 100644 index 0000000..449b03e --- /dev/null +++ b/coffee/step6_file.coffee @@ -0,0 +1,90 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + loop + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + return env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + ast = a2 + env = let_env + when "do" + eval_ast(ast[1..-2], env) + ast = ast[ast.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then ast = a3 else return null + else + ast = a2 + when "fn*" + return types._function(EVAL, a2, env, a1) + else + [f, args...] = eval_ast ast, env + if types._function_Q(f) + ast = f.__ast__ + env = f.__gen_env__(args) + else + return f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns +repl_env.set 'eval', (ast) -> EVAL(ast, repl_env) +repl_env.set '*ARGV*', [] + +# core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +if process? && process.argv.length > 2 + repl_env.set '*ARGV*', process.argv[3..] + rep('(load-file "' + process.argv[2] + '")') + process.exit 0 + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step7_quote.coffee b/coffee/step7_quote.coffee new file mode 100644 index 0000000..404e684 --- /dev/null +++ b/coffee/step7_quote.coffee @@ -0,0 +1,106 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +is_pair = (x) -> types._sequential_Q(x) && x.length > 0 + +quasiquote = (ast) -> + if !is_pair(ast) then [types._symbol('quote'), ast] + else if ast[0].name == 'unquote' then ast[1] + else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote' + [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])] + else + [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])] + + + +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + loop + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + return env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + ast = a2 + env = let_env + when "quote" + return a1 + when "quasiquote" + ast = quasiquote(a1) + when "do" + eval_ast(ast[1..-2], env) + ast = ast[ast.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then ast = a3 else return null + else + ast = a2 + when "fn*" + return types._function(EVAL, a2, env, a1) + else + [f, args...] = eval_ast ast, env + if types._function_Q(f) + ast = f.__ast__ + env = f.__gen_env__(args) + else + return f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns +repl_env.set 'eval', (ast) -> EVAL(ast, repl_env) +repl_env.set '*ARGV*', [] + +# core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +if process? && process.argv.length > 2 + repl_env.set '*ARGV*', process.argv[3..] + rep('(load-file "' + process.argv[2] + '")') + process.exit 0 + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step8_macros.coffee b/coffee/step8_macros.coffee new file mode 100644 index 0000000..39404e7 --- /dev/null +++ b/coffee/step8_macros.coffee @@ -0,0 +1,126 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +is_pair = (x) -> types._sequential_Q(x) && x.length > 0 + +quasiquote = (ast) -> + if !is_pair(ast) then [types._symbol('quote'), ast] + else if ast[0].name == 'unquote' then ast[1] + else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote' + [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])] + else + [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])] + +is_macro_call = (ast, env) -> + return types._list_Q(ast) && types._symbol_Q(ast[0]) && + env.find(ast[0].name) && env.get(ast[0].name).__ismacro__ + +macroexpand = (ast, env) -> + while is_macro_call(ast, env) + ast = env.get(ast[0].name)(ast[1..]...) + ast + + + +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + loop + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + ast = macroexpand ast, env + if !types._list_Q ast then return ast + + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + return env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + ast = a2 + env = let_env + when "quote" + return a1 + when "quasiquote" + ast = quasiquote(a1) + when "defmacro!" + f = EVAL(a2, env) + f.__ismacro__ = true + return env.set(a1.name, f) + when "macroexpand" + return macroexpand(a1, env) + when "do" + eval_ast(ast[1..-2], env) + ast = ast[ast.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then ast = a3 else return null + else + ast = a2 + when "fn*" + return types._function(EVAL, a2, env, a1) + else + [f, args...] = eval_ast ast, env + if types._function_Q(f) + ast = f.__ast__ + env = f.__gen_env__(args) + else + return f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns +repl_env.set 'eval', (ast) -> EVAL(ast, repl_env) +repl_env.set '*ARGV*', [] + +# core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +if process? && process.argv.length > 2 + repl_env.set '*ARGV*', process.argv[3..] + rep('(load-file "' + process.argv[2] + '")') + process.exit 0 + +# repl loop +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/step9_try.coffee b/coffee/step9_try.coffee new file mode 100644 index 0000000..d7919e7 --- /dev/null +++ b/coffee/step9_try.coffee @@ -0,0 +1,136 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +is_pair = (x) -> types._sequential_Q(x) && x.length > 0 + +quasiquote = (ast) -> + if !is_pair(ast) then [types._symbol('quote'), ast] + else if ast[0].name == 'unquote' then ast[1] + else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote' + [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])] + else + [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])] + +is_macro_call = (ast, env) -> + return types._list_Q(ast) && types._symbol_Q(ast[0]) && + env.find(ast[0].name) && env.get(ast[0].name).__ismacro__ + +macroexpand = (ast, env) -> + while is_macro_call(ast, env) + ast = env.get(ast[0].name)(ast[1..]...) + ast + + + +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + loop + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + ast = macroexpand ast, env + if !types._list_Q ast then return ast + + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + return env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + ast = a2 + env = let_env + when "quote" + return a1 + when "quasiquote" + ast = quasiquote(a1) + when "defmacro!" + f = EVAL(a2, env) + f.__ismacro__ = true + return env.set(a1.name, f) + when "macroexpand" + return macroexpand(a1, env) + when "try*" + try return EVAL(a1, env) + catch exc + if a2 && a2[0].name == "catch*" + if exc instanceof Error then exc = exc.message + return EVAL a2[2], new Env(env, [a2[1]], [exc]) + else + throw exc + when "do" + eval_ast(ast[1..-2], env) + ast = ast[ast.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then ast = a3 else return null + else + ast = a2 + when "fn*" + return types._function(EVAL, a2, env, a1) + else + [f, args...] = eval_ast ast, env + if types._function_Q(f) + ast = f.__ast__ + env = f.__gen_env__(args) + else + return f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns +repl_env.set 'eval', (ast) -> EVAL(ast, repl_env) +repl_env.set '*ARGV*', [] + +# core.mal: defined using the language itself +rep("(def! *host-language* \"CoffeeScript\")") +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +if process? && process.argv.length > 2 + repl_env.set '*ARGV*', process.argv[3..] + rep('(load-file "' + process.argv[2] + '")') + process.exit 0 + +# repl loop +rep("(println (str \"Mal [\" *host-language* \"]\"))") +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/stepA_interop.coffee b/coffee/stepA_interop.coffee new file mode 100644 index 0000000..aa9c5cc --- /dev/null +++ b/coffee/stepA_interop.coffee @@ -0,0 +1,142 @@ +readline = require "./node_readline.coffee" +types = require "./types.coffee" +reader = require "./reader.coffee" +printer = require "./printer.coffee" +Env = require("./env.coffee").Env +core = require("./core.coffee") + +# read +READ = (str) -> reader.read_str str + +# eval +is_pair = (x) -> types._sequential_Q(x) && x.length > 0 + +quasiquote = (ast) -> + if !is_pair(ast) then [types._symbol('quote'), ast] + else if ast[0].name == 'unquote' then ast[1] + else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote' + [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])] + else + [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])] + +is_macro_call = (ast, env) -> + return types._list_Q(ast) && types._symbol_Q(ast[0]) && + env.find(ast[0].name) && env.get(ast[0].name).__ismacro__ + +macroexpand = (ast, env) -> + while is_macro_call(ast, env) + ast = env.get(ast[0].name)(ast[1..]...) + ast + + + +eval_ast = (ast, env) -> + if types._symbol_Q(ast) then env.get ast.name + else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env)) + else if types._vector_Q(ast) + types._vector(ast.map((a) -> EVAL(a, env))...) + else if types._hash_map_Q(ast) + new_hm = {} + new_hm[k] = EVAL(ast[k],env) for k,v of ast + new_hm + else ast + +EVAL = (ast, env) -> + loop + #console.log "EVAL:", printer._pr_str ast + if !types._list_Q ast then return eval_ast ast, env + + # apply list + ast = macroexpand ast, env + if !types._list_Q ast then return ast + + [a0, a1, a2, a3] = ast + switch a0.name + when "def!" + return env.set(a1.name, EVAL(a2, env)) + when "let*" + let_env = new Env(env) + for k,i in a1 when i %% 2 == 0 + let_env.set(a1[i].name, EVAL(a1[i+1], let_env)) + ast = a2 + env = let_env + when "quote" + return a1 + when "quasiquote" + ast = quasiquote(a1) + when "defmacro!" + f = EVAL(a2, env) + f.__ismacro__ = true + return env.set(a1.name, f) + when "macroexpand" + return macroexpand(a1, env) + when "try*" + try return EVAL(a1, env) + catch exc + if a2 && a2[0].name == "catch*" + if exc instanceof Error then exc = exc.message + return EVAL a2[2], new Env(env, [a2[1]], [exc]) + else + throw exc + when "js*" + res = eval(a1.toString()) + return if typeof(res) == 'undefined' then null else res + when "." + el = eval_ast(ast[2..], env) + return eval(a1.toString())(el...) + when "do" + eval_ast(ast[1..-2], env) + ast = ast[ast.length-1] + when "if" + cond = EVAL(a1, env) + if cond == null or cond == false + if a3? then ast = a3 else return null + else + ast = a2 + when "fn*" + return types._function(EVAL, a2, env, a1) + else + [f, args...] = eval_ast ast, env + if types._function_Q(f) + ast = f.__ast__ + env = f.__gen_env__(args) + else + return f(args...) + + +# print +PRINT = (exp) -> printer._pr_str exp, true + +# repl +repl_env = new Env() +rep = (str) -> PRINT(EVAL(READ(str), repl_env)) + +# core.coffee: defined using CoffeeScript +repl_env.set k, v for k,v of core.ns +repl_env.set 'eval', (ast) -> EVAL(ast, repl_env) +repl_env.set '*ARGV*', [] + +# core.mal: defined using the language itself +rep("(def! *host-language* \"CoffeeScript\")") +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +if process? && process.argv.length > 2 + repl_env.set '*ARGV*', process.argv[3..] + rep('(load-file "' + process.argv[2] + '")') + process.exit 0 + +# repl loop +rep("(println (str \"Mal [\" *host-language* \"]\"))") +while (line = readline.readline("user> ")) != null + continue if line == "" + try + console.log rep line + catch exc + continue if exc instanceof reader.BlankException + if exc.stack then console.log exc.stack + else console.log exc + +# vim: ts=2:sw=2 diff --git a/coffee/tests/stepA_interop.mal b/coffee/tests/stepA_interop.mal new file mode 100644 index 0000000..f785292 --- /dev/null +++ b/coffee/tests/stepA_interop.mal @@ -0,0 +1,24 @@ +;; Testing basic bash interop + +(js* "7") +;=>7 + +(js* "'7'") +;=>"7" + +(js* "[7,8,9]") +;=>(7 8 9) + +(js* "console.log('hello');") +; hello +;=>nil + +(js* "foo=8;") +(js* "foo;") +;=>8 + +(js* "['a','b','c'].map(function(x){return 'X'+x+'Y'}).join(' ')") +;=>"XaY XbY XcY" + +(js* "[1,2,3].map(function(x){return 1+x})") +;=>(2 3 4) diff --git a/coffee/types.coffee b/coffee/types.coffee new file mode 100644 index 0000000..8982120 --- /dev/null +++ b/coffee/types.coffee @@ -0,0 +1,115 @@ +Env = require("./env.coffee").Env + +E = exports + +# General functions +E._obj_type = _obj_type = (obj) -> + if _symbol_Q(obj) then 'symbol' + else if _list_Q(obj) then 'list' + else if _vector_Q(obj) then 'vector' + else if _hash_map_Q(obj) then 'hash-map' + else if _nil_Q(obj) then 'nil' + else if _true_Q(obj) then 'true' + else if _false_Q(obj) then 'false' + else if _atom_Q(obj) then 'atom' + else + switch typeof obj + when 'number' then 'number' + when 'function' then 'function' + when 'string' then 'string' + else throw new Error "Unknown type '" + typeof(obj) + "'" + +E._sequential_Q = _sequential_Q = (o) -> _list_Q(o) or _vector_Q(o) + +E._equal_Q = _equal_Q = (a,b) -> + [ota, otb] = [_obj_type(a), _obj_type(b)] + if !(ota == otb or (_sequential_Q(a) && _sequential_Q(b))) + return false + switch (ota) + when 'symbol' then a.name == b.name + when 'list', 'vector' + return false if a.length != b.length + for av,i in a + return false if !_equal_Q(av, b[i]) + true + when 'hash-map' + akeys = (key for key of a) + bkeys = (key for key of b) + return false if akeys.length != bkeys.length + for akey,i in akeys + bkey = bkeys[i] + return false if akey != bkey + return false if !_equal_Q(a[akey], b[bkey]) + true + else a == b + +E._clone = _clone = (obj) -> + switch _obj_type(obj) + when 'list' then obj[0..-1] + when 'vector' then _vector(obj[0..-1]...) + when 'hash-map' + new_obj = {} + new_obj[k] = v for k,v of obj + new_obj + when 'function' + new_obj = (args...) -> obj(args...) + new_obj[k] = v for k,v of obj + new_obj + else throw new Error "clone called on non-collection" + _obj_type(obj) + + +# Scalars +E._nil_Q = _nil_Q = (o) -> o == null +E._true_Q = _true_Q = (o) -> o == true +E._false_Q = _false_Q = (o) -> o == false + +# Symbols +class Symbol + constructor: (@name) -> +E._symbol = (str) -> new Symbol str +E._symbol_Q = _symbol_Q = (o) -> o instanceof Symbol + +# Functions +E._function = (evalfn, ast, env, params) -> + fn = (args...) -> evalfn(ast, new Env(env, params, args)) + fn.__ast__ = ast + fn.__gen_env__ = (args) -> new Env(env, params, args) + fn.__ismacro__ = false + fn +E._function_Q = _function_Q = (o) -> !!o.__ast__ + +# Lists +E._list_Q = _list_Q = (o) -> Array.isArray(o) && !o.__isvector__ + +# Vectors +E._vector = _vector = (args...) -> + v = args + v.__isvector__ = true + v +E._vector_Q = _vector_Q = (o) -> Array.isArray(o) && !!o.__isvector__ + +# Hash Maps +E._hash_map = (args...) -> + args = [{}].concat args + _assoc_BANG(args...) +E._assoc_BANG = _assoc_BANG = (hm, args...) -> + if args.length %% 2 == 1 + throw new Error "Odd number of hash map arguments" + hm[k] = args[i+1] for k, i in args when i %% 2 == 0 + hm +E._dissoc_BANG = (hm, args...) -> + delete hm[k] for k, i in args + hm +E._hash_map_Q = _hash_map_Q = (o) -> + typeof o == "object" && !Array.isArray(o) && + !(o == null) && + !(o instanceof Atom) + + +# Atoms +class Atom + constructor: (@val) -> +E._atom = (val) -> new Atom val +E._atom_Q = _atom_Q = (o) -> o instanceof Atom + +# vim: ts=2:sw=2 diff --git a/tests/step8_macros.mal b/tests/step8_macros.mal index f2f62dc..8773ba8 100644 --- a/tests/step8_macros.mal +++ b/tests/step8_macros.mal @@ -1,4 +1,14 @@ -;; Testing first function +;; Testing nth, first and rest functions + +(nth '() 0) +;=>nil +(nth '(1) 0) +;=>1 +(nth '(1 2) 1) +;=>2 +(nth '(1 2) 2) +;=>nil + (first '()) ;=>nil (first '(6)) @@ -6,7 +16,6 @@ (first '(7 8 9)) ;=>7 -;; Testing rest function (rest '()) ;=>() (rest '(6)) |
