aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-10-06 20:36:23 -0500
committerJoel Martin <github@martintribe.org>2014-10-06 20:36:23 -0500
commitaf8fdff41e260b1b21be0e127afb536980f43804 (patch)
tree6dc9b5d54a38c6197001291cf85cdffc7cf100b7
parent9feb2c9527294d82592bf35b97f8039f61bbec45 (diff)
downloadmal-af8fdff41e260b1b21be0e127afb536980f43804.tar.gz
mal-af8fdff41e260b1b21be0e127afb536980f43804.zip
go: add step4_if_fn_do
-rw-r--r--docs/TODO5
-rw-r--r--docs/step_notes.txt92
-rw-r--r--go/Makefile7
-rw-r--r--go/src/core/core.go90
-rw-r--r--go/src/printer/printer.go18
-rw-r--r--go/src/reader/reader.go7
-rw-r--r--go/src/step3_env/step3_env.go28
-rw-r--r--go/src/step4_if_fn_do/step4_if_fn_do.go181
-rw-r--r--go/src/types/types.go39
9 files changed, 443 insertions, 24 deletions
diff --git a/docs/TODO b/docs/TODO
index ed461e1..c94715f 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -32,6 +32,11 @@ C#:
Clojure:
+Go:
+ - use reflect to simplify several points
+ - consider variable arguments in places where it makes sense
+ https://gobyexample.com/variadic-functions
+
Java:
- step9_interop
- Use gradle instead of mvn
diff --git a/docs/step_notes.txt b/docs/step_notes.txt
index 117470f..02ad305 100644
--- a/docs/step_notes.txt
+++ b/docs/step_notes.txt
@@ -30,6 +30,7 @@ Step Notes:
- types module:
- add boxed types if no language equivalent:
- nil, true, false, symbol, integer, string, list
+ - error types if necessary
- reader module:
- stateful reader object
- alternative: mutate token list
@@ -46,15 +47,42 @@ Step Notes:
- read_atom (not atom type)
- return scalar boxed type:
- nil, true, false, symbol, integer, string
+ - skip unquoting
- printer module:
- _pr_str:
- stringify boxed types to their Mal representations
- list/array is recursive
+ - skip quoting
- repl loop
- catch errors, print them and continue
- impls without exception handling will need to have a global
variable with checks for it at the beginning of critical
code sections
+ - Details:
+ - copy step0_repl.EXT to step1_read_print.EXT
+ - modify Makefile if compiled
+ - call reader.read_str from READ
+ - pass through type returned from read_str through
+ READ/EVAL/PRINT
+ - create reader.EXT
+ - if regex support (much easier) then tokenize with this:
+ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g
+ - add read_str:
+ - call tokenize
+ - handle blank line (exceptions, return code, global
+ depending on lang features)
+ - read_str -> read_form -> {read_list, read_atom}
+ - mutable reader thing
+ - create printer.EXT
+ - _pr_str function which basically reverses read_str and
+ returns a string representation
+ - run `make test^EXT^step1`. Much of the basics should pass up
+ to vectors
+ - implement read_hash_map (can refactor read_list)
+ - import read_vector
+ - probably want to define types for List and Vector in
+ types.EXT that extend or wrap native arrays
+ - run `make test^EXT^step1`. All mandatory should pass
- comments
@@ -95,8 +123,23 @@ Step Notes:
- EVAL/apply:
- if not a list, call eval_ast
- otherwise, apply first item to eval_ast of (rest ast)
- - repl_env as simple one level assoc. array (or hash_map)
+ - repl_env as simple one level hash map (assoc. array)
- store function as hash_map value
+ - Details:
+ - copy step1_read_print.EXT to step2_eval.EXT
+ - create repl_env hash_map) with +, -, *, /
+ - store anon func as values if possible
+ - types.EXT
+ - implement symbol? (symbol_Q) and list? (list_Q)
+ - add env param to EVAL and add to rep EVAL call
+ - EVAL
+ - if not list call eval_ast
+ - otherwise eval_ast, and call first arg with rest
+ - eval_ast
+ - if symbol?, lookup in env
+ - if List, EVAL each and return eval's list
+ - otherwise, return original
+ - optional: handle vector and hash-map in eval_ast
- vectors
- eval each item, return new vector
@@ -112,6 +155,22 @@ Step Notes:
- EVAL/apply:
- def! - mutate current environment
- let* - create new environment with bindings
+ - Details:
+ - cp step2_eval.EXT to step3_env.EXT
+ - add env.EXT if lang support file dep cycles, otherwise, add
+ to types.EXT
+ - Env type
+ - find, get, set methods/functions
+ - use Env type instead of map/assoc. array
+ - eval_ast: use method for lookup
+ - EVAL:
+ - switch on first symbol
+ - def!
+ - set env[a1] to EVAL(a2, env)
+ - let*
+ - loop through let building up let_env
+ - EVAL(a2, let_env)
+ - move apply to default
- step4_if_fn_do
- types module:
@@ -139,6 +198,37 @@ Step Notes:
- otherwise needs a way of representing functions that can
have associated metadata
- define "not" using REP/RE
+ - Details:
+ - cp step3_env.EXT to step4_env.EXT
+ - modify Makefile if compiled
+ - env.EXT
+ - add binds and exprs args. Create new environments with
+ exprs bound to binds. If & symbol, bind rest of exprs to
+ next bind symbol
+ - EVAL:
+ - do:
+ - eval_ast [1:], then return last eval'd element
+ - if
+ - EVAL(a1)
+ - if true EVAL(a2)
+ - else EVAL(a3), unless no a3 then return nil
+ - fn*
+ - if available use function closures to return a new
+ native function that calls EVAL(a2, Env(env, a1, fargs))
+ - core.EXT
+ - create ns object to hold core namespace
+ - move numeric operators here
+ - add compison operators
+ - add list, list?, empty?, count, not
+ - run make test^EXT^step4
+ - implement equal?/equal_Q in types.EXT and refer in core.ns
+ - implement not as rep("(def! not (fn* (a) (if a false true)))")
+ - run make test^EXT^step4: should pass everything except
+ string routines
+ - implement: pr-str, str, prn, println in core.EXT and
+ refer in core.ns
+ - should leverage pr-str from printer.EXT
+ - add reader/printer string quote/unquote
- step5_tco
- types module:
diff --git a/go/Makefile b/go/Makefile
index 695627e..a029af7 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -2,13 +2,14 @@ export GOPATH := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
#####################
-SOURCES_BASE = src/types/types.go src/reader/reader.go src/printer/printer.go src/env/env.go
-SOURCES_LISP = src/step3_env/step3_env.go
+SOURCES_BASE = src/types/types.go src/reader/reader.go src/printer/printer.go \
+ src/env/env.go src/core/core.go
+SOURCES_LISP = src/step4_if_fn_do/step4_if_fn_do.go
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
#####################
-SRCS = step0_repl.go step1_read_print.go step2_eval.go step3_env.go
+SRCS = step0_repl.go step1_read_print.go step2_eval.go step3_env.go step4_if_fn_do.go
BINS = $(SRCS:%.go=%)
#####################
diff --git a/go/src/core/core.go b/go/src/core/core.go
new file mode 100644
index 0000000..9df71a6
--- /dev/null
+++ b/go/src/core/core.go
@@ -0,0 +1,90 @@
+package core
+
+import (
+ "errors"
+ "fmt"
+)
+
+import (
+ . "types"
+ "printer"
+)
+
+
+// String functions
+
+func pr_str(a []MalType) (MalType, error) {
+ return printer.Pr_list(a, true, "", "", " "), nil
+}
+
+func str(a []MalType) (MalType, error) {
+ return printer.Pr_list(a, false, "", "", ""), nil
+}
+
+func prn(a []MalType) (MalType, error) {
+ fmt.Println(printer.Pr_list(a, true, "", "", " "))
+ return nil, nil
+}
+
+func println(a []MalType) (MalType, error) {
+ fmt.Println(printer.Pr_list(a, false, "", "", " "))
+ return nil, nil
+}
+
+
+// Sequence functions
+
+func empty_Q(a []MalType) (MalType, error) {
+ switch obj := a[0].(type) {
+ case List: return len(obj.Val) == 0, nil
+ case Vector: return len(obj.Val) == 0, nil
+ case nil: return true, nil
+ default: return nil, errors.New("Count called on non-sequence")
+ }
+}
+
+func count(a []MalType) (MalType, error) {
+ switch obj := a[0].(type) {
+ case List: return len(obj.Val), nil
+ case Vector: return len(obj.Val), nil
+ case nil: return 0, nil
+ default: return nil, errors.New("Count called on non-sequence")
+ }
+}
+
+
+// core namespace
+var NS = map[string]MalType{
+ "=": func(a []MalType) (MalType, error) {
+ return Equal_Q(a[0], a[1]), nil },
+
+ "pr-str": func(a []MalType) (MalType, error) { return pr_str(a) },
+ "str": func(a []MalType) (MalType, error) { return str(a) },
+ "prn": func(a []MalType) (MalType, error) { return prn(a) },
+ "println": func(a []MalType) (MalType, error) { return println(a) },
+
+ "<": func(a []MalType) (MalType, error) {
+ return a[0].(int) < a[1].(int), nil },
+ "<=": func(a []MalType) (MalType, error) {
+ return a[0].(int) <= a[1].(int), nil },
+ ">": func(a []MalType) (MalType, error) {
+ return a[0].(int) > a[1].(int), nil },
+ ">=": func(a []MalType) (MalType, error) {
+ return a[0].(int) >= a[1].(int), nil },
+ "+": func(a []MalType) (MalType, error) {
+ return a[0].(int) + a[1].(int), nil },
+ "-": func(a []MalType) (MalType, error) {
+ return a[0].(int) - a[1].(int), nil },
+ "*": func(a []MalType) (MalType, error) {
+ return a[0].(int) * a[1].(int), nil },
+ "/": func(a []MalType) (MalType, error) {
+ return a[0].(int) / a[1].(int), nil },
+
+ "list": func(a []MalType) (MalType, error) {
+ return List{a}, nil },
+ "list?": func(a []MalType) (MalType, error) {
+ return List_Q(a[0]), nil },
+
+ "empty?": empty_Q,
+ "count": count,
+ }
diff --git a/go/src/printer/printer.go b/go/src/printer/printer.go
index fa1c0fb..8cb4a40 100644
--- a/go/src/printer/printer.go
+++ b/go/src/printer/printer.go
@@ -9,20 +9,21 @@ import (
"types"
)
-func _pr_list(lst []types.MalType, pr bool, start string, end string) string {
+func Pr_list(lst []types.MalType, pr bool,
+ start string, end string, join string) string {
str_list := make([]string, 0, len(lst))
for _, e := range lst {
str_list = append(str_list, Pr_str(e, pr))
}
- return start + strings.Join(str_list, " ") + end
+ return start + strings.Join(str_list, join) + end
}
func Pr_str(obj types.MalType, print_readably bool) string {
switch tobj := obj.(type) {
case types.List:
- return _pr_list(tobj.Val, print_readably, "(", ")")
+ return Pr_list(tobj.Val, print_readably, "(", ")", " ")
case types.Vector:
- return _pr_list(tobj.Val, print_readably, "[", "]")
+ return Pr_list(tobj.Val, print_readably, "[", "]", " ")
case map[string]types.MalType:
str_list := make([]string, 0, len(tobj)*2)
for k, v := range tobj {
@@ -32,10 +33,13 @@ func Pr_str(obj types.MalType, print_readably bool) string {
return "{" + strings.Join(str_list, " ") + "}"
case string:
if print_readably {
- // TODO: quote backslash, quote, and newline
- return `"` + fmt.Sprintf("%v", obj) + `"`
+ return `"` + strings.Replace(
+ strings.Replace(
+ strings.Replace(tobj,`\`,`\\`, -1),
+ `"`, `\"`, -1),
+ "\n", `\n`, -1) + `"`
} else {
- return fmt.Sprintf("%v", obj)
+ return tobj
}
case types.Symbol:
return tobj.Val
diff --git a/go/src/reader/reader.go b/go/src/reader/reader.go
index 55cea59..d4b7510 100644
--- a/go/src/reader/reader.go
+++ b/go/src/reader/reader.go
@@ -4,6 +4,7 @@ import (
"errors"
"regexp"
"strconv"
+ "strings"
//"fmt"
)
@@ -56,8 +57,10 @@ func read_atom(rdr Reader) (types.MalType, error) {
}
return i, nil
} else if (*token)[0] == '"' {
- // TODO: unquote newline and quotes
- return (*token)[1:len(*token)-1], nil
+ str := (*token)[1:len(*token)-1]
+ return strings.Replace(
+ strings.Replace(str, `\"`, `"`, -1),
+ `\n`, "\n", -1), nil
} else if *token == "nil" {
return nil, nil
} else if *token == "true" {
diff --git a/go/src/step3_env/step3_env.go b/go/src/step3_env/step3_env.go
index 6241eff..3d35dbe 100644
--- a/go/src/step3_env/step3_env.go
+++ b/go/src/step3_env/step3_env.go
@@ -70,30 +70,36 @@ func EVAL(ast MalType, env Env) (MalType, error) {
// apply list
a0 := ast.(List).Val[0]
- if !Symbol_Q(a0) {
- return nil, errors.New("attempt to apply on non-symbol '" +
- printer.Pr_str(a0, true) + "'")
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
}
- switch a0.(Symbol).Val {
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
case "def!":
- a1 := ast.(List).Val[1]; a2 := ast.(List).Val[2]
res, e := EVAL(a2, env)
if e != nil { return nil, e }
return env.Set(a1.(Symbol).Val, res), nil
case "let*":
- a1 := ast.(List).Val[1]; a2 := ast.(List).Val[2]
- let_env := NewEnv(&env, nil, nil)
+ let_env, e := NewEnv(&env, nil, nil)
+ if e != nil { return nil, e }
arr1, e := GetSlice(a1)
if e != nil { return nil, e }
for i := 0; i < len(arr1); i+=2 {
if !Symbol_Q(arr1[i]) {
return nil, errors.New("non-symbol bind value")
}
- exp, e := EVAL(arr1[i+1], let_env)
+ exp, e := EVAL(arr1[i+1], *let_env)
if e != nil { return nil, e }
let_env.Set(arr1[i].(Symbol).Val, exp)
}
- return EVAL(a2, let_env)
+ return EVAL(a2, *let_env)
default:
el, e := eval_ast(ast, env)
if e != nil { return nil, e }
@@ -109,14 +115,14 @@ func PRINT(exp MalType) (MalType, error) {
}
-var repl_env = NewEnv(nil, nil, nil)
+var repl_env, _ = NewEnv(nil, nil, nil)
// repl
func rep(str string) (MalType, error) {
var exp MalType
var e error
if exp, e = READ(str); e != nil { return nil, e }
- if exp, e = EVAL(exp, repl_env); e != nil { return nil, e }
+ if exp, e = EVAL(exp, *repl_env); e != nil { return nil, e }
if exp, e = PRINT(exp); e != nil { return nil, e }
return exp, nil
}
diff --git a/go/src/step4_if_fn_do/step4_if_fn_do.go b/go/src/step4_if_fn_do/step4_if_fn_do.go
new file mode 100644
index 0000000..f0cc41f
--- /dev/null
+++ b/go/src/step4_if_fn_do/step4_if_fn_do.go
@@ -0,0 +1,181 @@
+package main
+
+import (
+ "bufio"
+ //"io"
+ "fmt"
+ "os"
+ "strings"
+ "errors"
+)
+
+import (
+ . "types"
+ "reader"
+ "printer"
+ . "env"
+ "core"
+)
+
+// read
+func READ(str string) (MalType, error) {
+ return reader.Read_str(str)
+}
+
+// eval
+func eval_ast(ast MalType, env Env) (MalType, error) {
+ //fmt.Printf("eval_ast: %#v\n", ast)
+ if Symbol_Q(ast) {
+ return env.Get(ast.(Symbol).Val)
+ } else if List_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(List).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return List{lst}, nil
+ } else if Vector_Q(ast) {
+ lst := []MalType{}
+ for _, a := range ast.(Vector).Val {
+ exp, e := EVAL(a, env)
+ if e != nil { return nil, e }
+ lst = append(lst, exp)
+ }
+ return Vector{lst}, nil
+ } else if Hash_Map_Q(ast) {
+ m := ast.(map[string]MalType)
+ new_hm := map[string]MalType{}
+ for k, v := range m {
+ ke, e1 := EVAL(k, env)
+ if e1 != nil { return nil, e1 }
+ if _, ok := ke.(string); !ok {
+ return nil, errors.New("non string hash-map key")
+ }
+ kv, e2 := EVAL(v, env)
+ if e2 != nil { return nil, e2 }
+ new_hm[ke.(string)] = kv
+ }
+ return new_hm, nil
+ } else {
+ return ast, nil
+ }
+}
+
+func EVAL(ast MalType, env Env) (MalType, error) {
+ //fmt.Printf("EVAL: %#v\n", ast)
+ switch ast.(type) {
+ case List: // continue
+ default: return eval_ast(ast, env)
+ }
+
+ // apply list
+ a0 := ast.(List).Val[0]
+ var a1 MalType = nil; var a2 MalType = nil
+ switch len(ast.(List).Val) {
+ case 1:
+ a1 = nil; a2 = nil
+ case 2:
+ a1 = ast.(List).Val[1]; a2 = nil
+ default:
+ a1 = ast.(List).Val[1]; a2 = ast.(List).Val[2]
+ }
+ a0sym := "__<*fn*>__"
+ if Symbol_Q(a0) { a0sym = a0.(Symbol).Val }
+ switch a0sym {
+ case "def!":
+ res, e := EVAL(a2, env)
+ if e != nil { return nil, e }
+ return env.Set(a1.(Symbol).Val, res), nil
+ case "let*":
+ let_env, e := NewEnv(&env, nil, nil)
+ if e != nil { return nil, e }
+ arr1, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ for i := 0; i < len(arr1); i+=2 {
+ if !Symbol_Q(arr1[i]) {
+ return nil, errors.New("non-symbol bind value")
+ }
+ exp, e := EVAL(arr1[i+1], *let_env)
+ if e != nil { return nil, e }
+ let_env.Set(arr1[i].(Symbol).Val, exp)
+ }
+ return EVAL(a2, *let_env)
+ case "do":
+ el, e := eval_ast(List{ast.(List).Val[1:]}, env)
+ if e != nil { return nil, e }
+ lst := el.(List).Val
+ if len(lst) == 0 { return nil, nil }
+ return lst[len(lst)-1], nil
+ case "if":
+ cond, e := EVAL(a1, env)
+ if e != nil { return nil, e }
+ if cond == nil || cond == false {
+ if len(ast.(List).Val) >= 4 {
+ return EVAL(ast.(List).Val[3], env)
+ } else {
+ return nil, nil
+ }
+ } else {
+ return EVAL(a2, env)
+ }
+ case "fn*":
+ return func(arguments []MalType) (MalType, error) {
+ a1s, e := GetSlice(a1)
+ if e != nil { return nil, e }
+ new_env, e := NewEnv(&env, a1s, arguments)
+ if e != nil { return nil, e }
+ return EVAL(a2, *new_env)
+ }, nil
+ default:
+ el, e := eval_ast(ast, env)
+ if e != nil { return nil, e }
+ f, ok := el.(List).Val[0].(func([]MalType)(MalType, error))
+ if !ok { return nil, errors.New("attempt to call non-function") }
+ return f(el.(List).Val[1:])
+ }
+}
+
+// print
+func PRINT(exp MalType) (MalType, error) {
+ return printer.Pr_str(exp, true), nil
+}
+
+
+var repl_env, _ = NewEnv(nil, nil, nil)
+
+// repl
+func rep(str string) (MalType, error) {
+ var exp MalType
+ var e error
+ if exp, e = READ(str); e != nil { return nil, e }
+ if exp, e = EVAL(exp, *repl_env); e != nil { return nil, e }
+ if exp, e = PRINT(exp); e != nil { return nil, e }
+ return exp, nil
+}
+
+func main() {
+ for k, v := range core.NS {
+ repl_env.Set(k, v)
+ }
+ rep("(def! not (fn* (a) (if a false true)))")
+
+ rdr := bufio.NewReader(os.Stdin);
+ // repl loop
+ for {
+ fmt.Print("user> ");
+ text, err := rdr.ReadString('\n');
+ text = strings.TrimRight(text, "\n");
+ if (err != nil) {
+ return
+ }
+ var out MalType
+ var e error
+ if out, e = rep(text); e != nil {
+ if e.Error() == "<empty line>" { continue }
+ fmt.Printf("Error: %v\n", e)
+ continue
+ }
+ fmt.Printf("%v\n", out)
+ }
+}
diff --git a/go/src/types/types.go b/go/src/types/types.go
index 2eacaaf..95bdc1f 100644
--- a/go/src/types/types.go
+++ b/go/src/types/types.go
@@ -1,7 +1,9 @@
package types
import (
+ "reflect"
"errors"
+ //"fmt"
)
//type Error interface {
@@ -62,3 +64,40 @@ func Hash_Map_Q(obj MalType) bool {
default: return false
}
}
+
+// General functions
+
+func _obj_type(obj MalType) string {
+ return reflect.TypeOf(obj).Name()
+}
+
+func Sequential_Q(seq MalType) bool {
+ //fmt.Printf("here1 %#v\n", reflect.TypeOf(seq).Name())
+ return (reflect.TypeOf(seq).Name() == "List") ||
+ (reflect.TypeOf(seq).Name() == "Vector")
+}
+
+func Equal_Q(a MalType, b MalType) bool {
+ ota := reflect.TypeOf(a); otb := reflect.TypeOf(b)
+ if !((ota == otb) || (Sequential_Q(a) && Sequential_Q(b))) {
+ return false
+ }
+ //av := reflect.ValueOf(a); bv := reflect.ValueOf(b)
+ //fmt.Printf("here2: %#v\n", reflect.TypeOf(a).Name())
+ switch reflect.TypeOf(a).Name() {
+ case "Symbol":
+ return a.(Symbol).Val == b.(Symbol).Val
+ case "List": fallthrough
+ 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 "map[string]MalType":
+ return false
+ default:
+ return a == b
+ }
+}