diff options
| author | Joel Martin <github@martintribe.org> | 2014-10-06 20:36:23 -0500 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2014-10-06 20:36:23 -0500 |
| commit | af8fdff41e260b1b21be0e127afb536980f43804 (patch) | |
| tree | 6dc9b5d54a38c6197001291cf85cdffc7cf100b7 | |
| parent | 9feb2c9527294d82592bf35b97f8039f61bbec45 (diff) | |
| download | mal-af8fdff41e260b1b21be0e127afb536980f43804.tar.gz mal-af8fdff41e260b1b21be0e127afb536980f43804.zip | |
go: add step4_if_fn_do
| -rw-r--r-- | docs/TODO | 5 | ||||
| -rw-r--r-- | docs/step_notes.txt | 92 | ||||
| -rw-r--r-- | go/Makefile | 7 | ||||
| -rw-r--r-- | go/src/core/core.go | 90 | ||||
| -rw-r--r-- | go/src/printer/printer.go | 18 | ||||
| -rw-r--r-- | go/src/reader/reader.go | 7 | ||||
| -rw-r--r-- | go/src/step3_env/step3_env.go | 28 | ||||
| -rw-r--r-- | go/src/step4_if_fn_do/step4_if_fn_do.go | 181 | ||||
| -rw-r--r-- | go/src/types/types.go | 39 |
9 files changed, 443 insertions, 24 deletions
@@ -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 + } +} |
