aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-10-09 22:10:15 -0500
committerJoel Martin <github@martintribe.org>2015-01-06 21:57:02 -0600
commit1771ab50b87c745181e4e30f94b63e3f23d33dac (patch)
treec1c9b5d80ba9261c5f16bab1b52c1bb0f559407c
parentf2544a9467ea032aff505b3ced3b4b3510a828fe (diff)
downloadmal-1771ab50b87c745181e4e30f94b63e3f23d33dac.tar.gz
mal-1771ab50b87c745181e4e30f94b63e3f23d33dac.zip
go: update README. Backport Func usage.
-rw-r--r--.gitignore1
-rw-r--r--README.md12
-rw-r--r--docs/TODO6
-rw-r--r--docs/step_notes.txt40
-rw-r--r--go/src/core/core.go33
-rw-r--r--go/src/step5_tco/step5_tco.go6
-rw-r--r--go/src/step6_file/step6_file.go15
-rw-r--r--go/src/step7_quote/step7_quote.go15
-rw-r--r--go/src/step8_macros/step8_macros.go17
-rw-r--r--go/src/stepA_more/stepA_more.go19
-rw-r--r--go/src/types/types.go29
-rw-r--r--tests/step1_read_print.mal1
-rw-r--r--tests/step7_quote.mal5
13 files changed, 165 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore
index 0061f6e..691f079 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ cs/*.dll
cs/*.mdb
clojure/target
clojure/.lein-repl-history
+go/step*
java/target/
java/dependency-reduced-pom.xml
rust/step0_repl
diff --git a/README.md b/README.md
index f03bf83..44fb631 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,13 @@
## Description
Mal is an interpreter for a subset of the Clojure programming
-language. Mal is implemented from scratch in 13 different languages:
+language. Mal is implemented from scratch in 14 different languages:
* Bash shell
* C
* C#
* Clojure
+* Go
* Java
* Javascript ([Online Demo](http://kanaka.github.io/mal))
* GNU Make
@@ -85,6 +86,15 @@ cd clojure
lein with-profile +stepX trampoline run
```
+### Go
+
+```
+cd go
+make
+./stepX_YYY
+```
+
+
### Java 1.7
```
diff --git a/docs/TODO b/docs/TODO
index c94715f..2464293 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -33,7 +33,6 @@ C#:
Clojure:
Go:
- - use reflect to simplify several points
- consider variable arguments in places where it makes sense
https://gobyexample.com/variadic-functions
@@ -110,6 +109,7 @@ Future Implementations:
* Shell (Bash 4)
- Tier 2
+ - R
- Scala
- Haskell
* Clojure
@@ -142,4 +142,8 @@ Future Implementations:
- Others:
- Forth (Stack-based)
- BF (Crazy)
+ - TeX/LaTeX
+ - Basic interpreter in TeX: http://ctanhg.scharrer-online.de/pkg/basix.html
+ - Cheat Sheet: http://www.stdout.org/~winston/latex/latexsheet.pd
+ - latex '\nonstopmode\input' blah.tex
diff --git a/docs/step_notes.txt b/docs/step_notes.txt
index 94665d8..44cf8f8 100644
--- a/docs/step_notes.txt
+++ b/docs/step_notes.txt
@@ -323,10 +323,23 @@ Step Notes:
- EVAL:
- add 'defmacro!' and 'macroexpand'
- set ismacro property on function
+ - Details:
+ - cp step7_quote.EXT to step8_macros.EXT
+ - if compiled update Makefile
+ - add isMacro property to Mal Function type
+ - may need to go back and adjust step5-7
+ - implement is_macro_call and macroexpand
+ - call macroexpand on ast before apply in EVAL
+ - add defmacro! and macroexpand to EVAL switch
+ - make test^go^step8 should pass some basic macros
+ - add first, rest and nth to core.ns
+ - make test^go^step8 should now pass
- step9_interop
- convert returned data to mal data
- recursive, similar to pr_str
+ - Details:
+ - can be done after stepA_more
- stepA_more
- core module:
@@ -340,6 +353,33 @@ Step Notes:
otherwise extracts full value
- set and print *host-language*
- define cond and or macros using REP/RE
+ - Details:
+ - cp step8_macros.EXT to stepA_more.EXT
+ - if compiled update Makefile
+ - core.ns implement nil?, true?, false?, symbol?, sequential?,
+ vector, vector?
+ - add mal error type which wraps normal mal type
+ - in core.ns add throw which wraps type in mal error type
+ and throws/raises/sets exception
+ - add try*/catch* support to EVAL
+ - if mal error type, bind to catch* bind symbol
+ - otherwise, bind string of error to catch* bind symbol
+ - implement apply, map in core.ns
+ - make test^go^stepA
+ - implement readline.EXT
+ - provide option (e.g. commented out) to link with GNU
+ readline (GPL) or libedit (BSD)
+ - add hash-map functions: hash-map, map?, assoc, dissoc, get,
+ contains?, keys, vals
+ - add metadata support to List, Vector, HashMap, and Functions
+ - add reader macro
+ - may need to box HashMap and native functions
+ - add atom type, reader macro and functions: with_meta, meta
+ - get `make test^go^stepA` to fully pass
+ - get `./stepA_more ../mal/step1_read_print` to pass
+ - continue for each mal step until ../mal/stepA_more
+ - Now self-hosting!
+
- Extra defintions needed for self-hosting
- core module:
diff --git a/go/src/core/core.go b/go/src/core/core.go
index f5d5842..57cf932 100644
--- a/go/src/core/core.go
+++ b/go/src/core/core.go
@@ -195,6 +195,34 @@ func do_map(a []MalType) (MalType, error) {
return List{results,nil}, nil
}
+func conj(a []MalType) (MalType, error) {
+ if len(a) <2 { return nil, errors.New("conj requires at least 2 arguments") }
+ switch seq := a[0].(type) {
+ case List:
+ new_slc := []MalType{}
+ for i := len(a)-1 ; i > 0 ; i-=1 {
+ new_slc = append(new_slc, a[i])
+ }
+ return List{append(new_slc, seq.Val...),nil}, nil
+ case Vector:
+ new_slc := seq.Val
+ for _, x := range a[1:] {
+ new_slc = append(new_slc, x)
+ }
+ return Vector{new_slc,nil}, nil
+ }
+
+ if !HashMap_Q(a[0]) { return nil, errors.New("dissoc called on non-hash map") }
+ new_hm := copy_hash_map(a[0].(HashMap))
+ for i := 1; i < len(a); i+=1 {
+ key := a[i]
+ if !String_Q(key) { return nil, errors.New("dissoc called with non-string key") }
+ delete(new_hm.Val,key.(string))
+ }
+ return new_hm, nil
+}
+
+
// Metadata functions
func with_meta(a []MalType) (MalType, error) {
@@ -204,6 +232,7 @@ func with_meta(a []MalType) (MalType, error) {
case List: return List{tobj.Val,m}, nil
case Vector: return Vector{tobj.Val,m}, nil
case HashMap: return HashMap{tobj.Val,m}, nil
+ case Func: return Func{tobj.Fn,m}, nil
case MalFunc: fn := tobj; fn.Meta = m; return fn, nil
default: return nil, errors.New("with-meta not supported on type")
}
@@ -215,6 +244,7 @@ func meta(a []MalType) (MalType, error) {
case List: return tobj.Meta, nil
case Vector: return tobj.Meta, nil
case HashMap: return tobj.Meta, nil
+ case Func: return tobj.Meta, nil
case MalFunc: return tobj.Meta, nil
default: return nil, errors.New("meta not supported on type")
}
@@ -319,11 +349,14 @@ var NS = map[string]MalType{
"count": count,
"apply": apply,
"map": do_map,
+ "conj": conj,
"with-meta": with_meta,
"meta": meta,
"atom": func(a []MalType) (MalType, error) {
return &Atom{a[0],nil}, nil },
+ "atom?": func(a []MalType) (MalType, error) {
+ return Atom_Q(a[0]), nil },
"deref": deref,
"reset!": reset_BANG,
"swap!": swap_BANG,
diff --git a/go/src/step5_tco/step5_tco.go b/go/src/step5_tco/step5_tco.go
index 344a533..9e176bb 100644
--- a/go/src/step5_tco/step5_tco.go
+++ b/go/src/step5_tco/step5_tco.go
@@ -133,9 +133,9 @@ func EVAL(ast MalType, env EnvType) (MalType, error) {
env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
if e != nil { return nil, e }
} else {
- fn, ok := f.(func([]MalType)(MalType, error))
+ fn, ok := f.(Func)
if !ok { return nil, errors.New("attempt to call non-function") }
- return fn(el.(List).Val[1:])
+ return fn.Fn(el.(List).Val[1:])
}
}
@@ -164,7 +164,7 @@ func rep(str string) (MalType, error) {
func main() {
// core.go: defined using go
for k, v := range core.NS {
- repl_env.Set(k, v)
+ repl_env.Set(k, Func{v.(func([]MalType)(MalType,error)),nil})
}
// core.mal: defined using the language itself
diff --git a/go/src/step6_file/step6_file.go b/go/src/step6_file/step6_file.go
index 51fe3cb..748d158 100644
--- a/go/src/step6_file/step6_file.go
+++ b/go/src/step6_file/step6_file.go
@@ -134,9 +134,9 @@ func EVAL(ast MalType, env EnvType) (MalType, error) {
env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
if e != nil { return nil, e }
} else {
- fn, ok := f.(func([]MalType)(MalType, error))
+ fn, ok := f.(Func)
if !ok { return nil, errors.New("attempt to call non-function") }
- return fn(el.(List).Val[1:])
+ return fn.Fn(el.(List).Val[1:])
}
}
@@ -165,10 +165,10 @@ func rep(str string) (MalType, error) {
func main() {
// core.go: defined using go
for k, v := range core.NS {
- repl_env.Set(k, v)
+ repl_env.Set(k, Func{v.(func([]MalType)(MalType,error)),nil})
}
- repl_env.Set("eval", func(a []MalType) (MalType, error) {
- return EVAL(a[0], repl_env) })
+ repl_env.Set("eval", Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
repl_env.Set("*ARGV*", List{})
// core.mal: defined using the language itself
@@ -182,7 +182,10 @@ func main() {
args = append(args, a)
}
repl_env.Set("*ARGV*", List{args,nil})
- rep("(load-file \"" + os.Args[1] + "\")")
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
os.Exit(0)
}
diff --git a/go/src/step7_quote/step7_quote.go b/go/src/step7_quote/step7_quote.go
index abf7b45..999129e 100644
--- a/go/src/step7_quote/step7_quote.go
+++ b/go/src/step7_quote/step7_quote.go
@@ -167,9 +167,9 @@ func EVAL(ast MalType, env EnvType) (MalType, error) {
env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
if e != nil { return nil, e }
} else {
- fn, ok := f.(func([]MalType)(MalType, error))
+ fn, ok := f.(Func)
if !ok { return nil, errors.New("attempt to call non-function") }
- return fn(el.(List).Val[1:])
+ return fn.Fn(el.(List).Val[1:])
}
}
@@ -198,10 +198,10 @@ func rep(str string) (MalType, error) {
func main() {
// core.go: defined using go
for k, v := range core.NS {
- repl_env.Set(k, v)
+ repl_env.Set(k, Func{v.(func([]MalType)(MalType,error)),nil})
}
- repl_env.Set("eval", func(a []MalType) (MalType, error) {
- return EVAL(a[0], repl_env) })
+ repl_env.Set("eval", Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
repl_env.Set("*ARGV*", List{})
// core.mal: defined using the language itself
@@ -215,7 +215,10 @@ func main() {
args = append(args, a)
}
repl_env.Set("*ARGV*", List{args,nil})
- rep("(load-file \"" + os.Args[1] + "\")")
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
os.Exit(0)
}
diff --git a/go/src/step8_macros/step8_macros.go b/go/src/step8_macros/step8_macros.go
index 0feb739..3264159 100644
--- a/go/src/step8_macros/step8_macros.go
+++ b/go/src/step8_macros/step8_macros.go
@@ -206,9 +206,9 @@ func EVAL(ast MalType, env EnvType) (MalType, error) {
env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
if e != nil { return nil, e }
} else {
- fn, ok := f.(func([]MalType)(MalType, error))
+ fn, ok := f.(Func)
if !ok { return nil, errors.New("attempt to call non-function") }
- return fn(el.(List).Val[1:])
+ return fn.Fn(el.(List).Val[1:])
}
}
@@ -237,15 +237,17 @@ func rep(str string) (MalType, error) {
func main() {
// core.go: defined using go
for k, v := range core.NS {
- repl_env.Set(k, v)
+ repl_env.Set(k, Func{v.(func([]MalType)(MalType,error)),nil})
}
- repl_env.Set("eval", func(a []MalType) (MalType, error) {
- return EVAL(a[0], repl_env) })
+ repl_env.Set("eval", Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
repl_env.Set("*ARGV*", List{})
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
+ rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
// called with mal script to load and eval
if len(os.Args) > 1 {
@@ -254,7 +256,10 @@ func main() {
args = append(args, a)
}
repl_env.Set("*ARGV*", List{args,nil})
- rep("(load-file \"" + os.Args[1] + "\")")
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
os.Exit(0)
}
diff --git a/go/src/stepA_more/stepA_more.go b/go/src/stepA_more/stepA_more.go
index d573ad0..322ee36 100644
--- a/go/src/stepA_more/stepA_more.go
+++ b/go/src/stepA_more/stepA_more.go
@@ -228,9 +228,9 @@ func EVAL(ast MalType, env EnvType) (MalType, error) {
env, e = NewEnv(fn.Env, fn.Params, List{el.(List).Val[1:],nil})
if e != nil { return nil, e }
} else {
- fn, ok := f.(func([]MalType)(MalType, error))
+ fn, ok := f.(Func)
if !ok { return nil, errors.New("attempt to call non-function") }
- return fn(el.(List).Val[1:])
+ return fn.Fn(el.(List).Val[1:])
}
}
@@ -259,15 +259,18 @@ func rep(str string) (MalType, error) {
func main() {
// core.go: defined using go
for k, v := range core.NS {
- repl_env.Set(k, v)
+ repl_env.Set(k, Func{v.(func([]MalType)(MalType,error)),nil})
}
- repl_env.Set("eval", func(a []MalType) (MalType, error) {
- return EVAL(a[0], repl_env) })
+ repl_env.Set("eval", Func{func(a []MalType) (MalType, error) {
+ return EVAL(a[0], repl_env) },nil})
repl_env.Set("*ARGV*", List{})
// core.mal: defined using the language itself
+ rep("(def! *host-language* \"go\")")
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
+ rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
// called with mal script to load and eval
if len(os.Args) > 1 {
@@ -276,11 +279,15 @@ func main() {
args = append(args, a)
}
repl_env.Set("*ARGV*", List{args,nil})
- rep("(load-file \"" + os.Args[1] + "\")")
+ if _,e := rep("(load-file \"" + os.Args[1] + "\")"); e != nil {
+ fmt.Printf("Error: %v\n", e)
+ os.Exit(1)
+ }
os.Exit(0)
}
// repl loop
+ rep("(println (str \"Mal [\" *host-language* \"]\"))")
for {
text, err := readline.Readline("user> ")
text = strings.TrimRight(text, "\n");
diff --git a/go/src/types/types.go b/go/src/types/types.go
index 616c8c7..613bfb5 100644
--- a/go/src/types/types.go
+++ b/go/src/types/types.go
@@ -65,6 +65,16 @@ func String_Q(obj MalType) bool {
// Functions
+type Func struct {
+ Fn func([]MalType) (MalType, error)
+ Meta MalType
+}
+
+func Func_Q(obj MalType) bool {
+ if obj == nil { return false }
+ return reflect.TypeOf(obj).Name() == "Func"
+}
+
type MalFunc struct {
Eval func(MalType, EnvType) (MalType, error)
Exp MalType
@@ -97,6 +107,8 @@ func Apply(f_mt MalType, a []MalType) (MalType, error) {
env, e := f.GenEnv(f.Env, f.Params, List{a,nil})
if e != nil { return nil, e }
return f.Eval(f.Exp, env)
+ case Func:
+ return f.Fn(a)
case func([]MalType)(MalType, error):
return f(a)
default:
@@ -206,18 +218,25 @@ func Equal_Q(a MalType, b MalType) bool {
}
//av := reflect.ValueOf(a); bv := reflect.ValueOf(b)
//fmt.Printf("here2: %#v\n", reflect.TypeOf(a).Name())
- switch reflect.TypeOf(a).Name() {
- case "Symbol":
+ //switch reflect.TypeOf(a).Name() {
+ switch a.(type) {
+ case Symbol:
return a.(Symbol).Val == b.(Symbol).Val
- case "List": fallthrough
- case "Vector":
+ case List:
+ as,_ := GetSlice(a); bs,_ := GetSlice(b)
+ if len(as) != len(bs) { return false }
+ for i := 0; i < len(as); i+=1 {
+ if !Equal_Q(as[i], bs[i]) { return false }
+ }
+ return true
+ case Vector:
as,_ := GetSlice(a); bs,_ := GetSlice(b)
if len(as) != len(bs) { return false }
for i := 0; i < len(as); i+=1 {
if !Equal_Q(as[i], bs[i]) { return false }
}
return true
- case "HashMap":
+ case HashMap:
return false
default:
return a == b
diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal
index 7772de9..2b7c269 100644
--- a/tests/step1_read_print.mal
+++ b/tests/step1_read_print.mal
@@ -78,6 +78,7 @@ abc-def
;;
;; Testing reader errors
+;;; TODO: fix these so they fail correctly
(1 2
; expected ')', got EOF
[1 2
diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal
index 9166065..38dac01 100644
--- a/tests/step7_quote.mal
+++ b/tests/step7_quote.mal
@@ -73,3 +73,8 @@
;=>false
(= "abc" 'abc)
;=>false
+
+;;;;; Test quine
+;;; TODO: needs expect line length fix
+;;;((fn* [q] (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* [q] (quasiquote ((unquote q) (quote (unquote q)))))))
+;;;=>((fn* [q] (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* [q] (quasiquote ((unquote q) (quote (unquote q)))))))