go: add step7_quote
authorJoel Martin <github@martintribe.org>
Tue, 7 Oct 2014 04:31:11 +0000 (23:31 -0500)
committerJoel Martin <github@martintribe.org>
Tue, 7 Oct 2014 04:31:11 +0000 (23:31 -0500)
docs/step_notes.txt
go/Makefile
go/src/core/core.go
go/src/reader/reader.go
go/src/step7_quote/step7_quote.go [new file with mode: 0644]

index af137d9..94665d8 100644 (file)
@@ -300,6 +300,15 @@ Step Notes:
     - reader module:
         - add reader macros to read_form for quote, unquote,
           splice-unquote and quasiquote
+    - Details:
+        - cp step6_file.EXT to step6_quote.EXT
+        - if compiled update Makefile
+        - add is_pair and quasiquote
+        - add quote and quasiquote cases to EVAL
+        - implement reader macros (', `, ~, ~@) in reader
+        - retest make test^go^step1
+        - implement cons and concat in core.EXT
+        - retest test^go^step7
 
 - step8_macros
     - types
index 8aebd88..ddf29f6 100644 (file)
@@ -4,13 +4,13 @@ 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 src/core/core.go
-SOURCES_LISP = src/step6_file/step6_file.go
+SOURCES_LISP = src/step7_quote/step7_quote.go
 SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
 
 #####################
 
 SRCS = step0_repl.go step1_read_print.go step2_eval.go step3_env.go \
-       step4_if_fn_do.go step5_tco.go step6_file.go
+       step4_if_fn_do.go step5_tco.go step6_file.go step7_quote.go
 BINS = $(SRCS:%.go=%)
 
 #####################
index 94e2452..65475bb 100644 (file)
@@ -42,6 +42,20 @@ func slurp(a []MalType) (MalType, error) {
 
 // Sequence functions
 
+func cons(a []MalType) (MalType, error) {
+    val := a[0]
+    lst, e := GetSlice(a[1]); if e != nil { return nil, e }
+
+    return List{append([]MalType{val}, lst...)}, nil
+}
+
+func concat(a []MalType) (MalType, error) {
+    lst1, e := GetSlice(a[0]); if e != nil { return nil, e }
+    lst2, e := GetSlice(a[1]); if e != nil { return nil, e }
+
+    return List{append(lst1, lst2...)}, nil
+}
+
 func empty_Q(a []MalType) (MalType, error) {
     switch obj := a[0].(type) {
     case List:   return len(obj.Val) == 0, nil
@@ -96,6 +110,8 @@ var NS = map[string]MalType{
     "list?": func(a []MalType) (MalType, error) {
             return List_Q(a[0]), nil },
 
+    "cons": cons,
+    "concat": concat,
     "empty?": empty_Q,
     "count": count,
     }
index 35aefd7..f8c15d5 100644 (file)
@@ -9,7 +9,7 @@ import (
 )
 
 import (
-    "types"
+    "types"
 )
 
 type Reader interface {
@@ -38,7 +38,10 @@ func (tr *TokenReader) peek() *string {
 
 func tokenize (str string) []string {
     results := make([]string, 0, 1)
-    re := regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('",;)]*)`)
+    // Work around lack of quoting in backtick
+    re := regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'` + "`" +
+                             `~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` + "`" + 
+                             `,;)]*)`)
     for _, group := range re.FindAllStringSubmatch(str, -1) {
         if (group[1] == "") || (group[1][0] == ';') { continue }
         results = append(results, group[1])
@@ -46,7 +49,7 @@ func tokenize (str string) []string {
     return results
 }
 
-func read_atom(rdr Reader) (types.MalType, error) {
+func read_atom(rdr Reader) (MalType, error) {
     token := rdr.next()
     if token == nil { return nil, errors.New("read_atom underflow") }
     if match, _ := regexp.MatchString(`^-?[0-9]+$`, *token); match {
@@ -68,16 +71,16 @@ func read_atom(rdr Reader) (types.MalType, error) {
     } else if *token == "false" {
         return false, nil
     } else {
-        return types.Symbol{*token}, nil
+        return Symbol{*token}, nil
     }
     return token, nil
 }
 
-func read_list(rdr Reader, start string, end string) (types.MalType, error) {
+func read_list(rdr Reader, start string, end string) (MalType, error) {
     token := rdr.next()
     if token == nil { return nil, errors.New("read_list underflow") }
 
-    ast_list := []types.MalType{}
+    ast_list := []MalType{}
     if *token != start {
         return nil, errors.New("expected '" + start + "'")
     }
@@ -90,24 +93,24 @@ func read_list(rdr Reader, start string, end string) (types.MalType, error) {
         ast_list = append(ast_list, f)
     }
     rdr.next()
-    return types.List{ast_list}, nil
+    return List{ast_list}, nil
 }
 
-func read_vector(rdr Reader) (types.MalType, error) {
+func read_vector(rdr Reader) (MalType, error) {
     lst, e := read_list(rdr, "[", "]")
     if e != nil { return nil, e }
-    vec := types.Vector{lst.(types.List).Val}
+    vec := Vector{lst.(List).Val}
     return vec, nil
 }
 
-func read_hash_map(rdr Reader) (types.MalType, error) {
+func read_hash_map(rdr Reader) (MalType, error) {
     mal_lst, e := read_list(rdr, "{", "}")
-    lst := mal_lst.(types.List).Val
+    lst := mal_lst.(List).Val
     if e != nil { return nil, e }
     if len(lst) % 2 == 1 {
         return nil, errors.New("Odd number of hash map arguments")
     }
-    m := map[string]types.MalType{}
+    m := map[string]MalType{}
     for i := 0; i < len(lst); i+=2 {
         str, ok := lst[i].(string)
         if !ok {
@@ -118,10 +121,24 @@ func read_hash_map(rdr Reader) (types.MalType, error) {
     return m, nil
 }
 
-func read_form(rdr Reader) (types.MalType, error) {
+func read_form(rdr Reader) (MalType, error) {
     token := rdr.peek()
     if token == nil { return nil, errors.New("read_form underflow") }
     switch (*token) {
+
+    case `'`:  rdr.next();
+               form, e := read_form(rdr); if e != nil { return nil, e }
+               return List{[]MalType{Symbol{"quote"}, form}}, nil
+    case "`":  rdr.next();
+               form, e := read_form(rdr); if e != nil { return nil, e }
+               return List{[]MalType{Symbol{"quasiquote"}, form}}, nil
+    case `~`:  rdr.next();
+               form, e := read_form(rdr); if e != nil { return nil, e }
+               return List{[]MalType{Symbol{"unquote"}, form}}, nil
+    case `~@`: rdr.next();
+               form, e := read_form(rdr); if e != nil { return nil, e }
+               return List{[]MalType{Symbol{"splice-unquote"}, form}}, nil
+
     // list
     case ")": return nil, errors.New("unexpected ')'")
     case "(": return read_list(rdr, "(", ")")
@@ -138,7 +155,7 @@ func read_form(rdr Reader) (types.MalType, error) {
     return read_atom(rdr)
 }
 
-func Read_str(str string) (types.MalType, error) {
+func Read_str(str string) (MalType, error) {
     var tokens = tokenize(str);
     if len(tokens) == 0 {
         return nil, errors.New("<empty line>")
diff --git a/go/src/step7_quote/step7_quote.go b/go/src/step7_quote/step7_quote.go
new file mode 100644 (file)
index 0000000..2acc9c7
--- /dev/null
@@ -0,0 +1,241 @@
+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 is_pair(x MalType) bool {
+    slc, e := GetSlice(x)
+    if e != nil { return false }
+    return len(slc) > 0
+}
+
+func quasiquote(ast MalType) MalType {
+    if !is_pair(ast) {
+        return List{[]MalType{Symbol{"quote"}, ast}}
+    } else {
+        slc, _ := GetSlice(ast)
+        a0 := slc[0]
+        if Symbol_Q(a0) && (a0.(Symbol).Val == "unquote") {
+            return slc[1]
+        } else if is_pair(a0) {
+            slc0, _ := GetSlice(a0)
+            a00 := slc0[0]
+            if Symbol_Q(a00) && (a00.(Symbol).Val == "splice-unquote") {
+                return List{[]MalType{Symbol{"concat"},
+                                      slc0[1],
+                                      quasiquote(List{slc[1:]})}}
+            }
+        }
+        return List{[]MalType{Symbol{"cons"},
+                              quasiquote(a0),
+                              quasiquote(List{slc[1:]})}}
+    }
+}
+
+func eval_ast(ast MalType, env EnvType) (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 EnvType) (MalType, error) {
+    for {
+
+    //fmt.Printf("EVAL: %v\n", printer.Pr_str(ast, true))
+    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)
+        }
+        ast = a2
+        env = let_env
+    case "quote":
+        return a1, nil
+    case "quasiquote":
+        ast = quasiquote(a1)
+    case "do":
+        lst := ast.(List).Val
+        _, e := eval_ast(List{lst[1:len(lst)-1]}, env) 
+        if e != nil { return nil, e }
+        if len(lst) == 1 { return nil, nil }
+        ast = lst[len(lst)-1]
+    case "if":
+        cond, e := EVAL(a1, env)
+        if e != nil { return nil, e }
+        if cond == nil || cond == false {
+            if len(ast.(List).Val) >= 4 {
+                ast = ast.(List).Val[3]
+            } else {
+                return nil, nil
+            }
+        } else {
+            ast = a2
+        }
+    case "fn*":
+        fn := MalFunc{EVAL, a2, env, a1}
+        return fn, nil
+    default:
+        el, e := eval_ast(ast, env)
+        if e != nil { return nil, e }
+        f := el.(List).Val[0]
+        if MalFunc_Q(f) {
+            fn := f.(MalFunc)
+            ast = fn.Exp
+            env, e = NewEnv(fn.Env, fn.Params.(List).Val, el.(List).Val[1:])
+            if e != nil { return nil, e }
+        } else {
+            fn, ok := f.(func([]MalType)(MalType, error))
+            if !ok { return nil, errors.New("attempt to call non-function") }
+            return fn(el.(List).Val[1:])
+        }
+    }
+
+    } // TCO loop
+}
+
+// print
+func PRINT(exp MalType) (string, 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 res string
+    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 res, e = PRINT(exp); e != nil { return nil, e }
+    return res, nil
+}
+
+func main() {
+    // core.go: defined using go
+    for k, v := range core.NS {
+        repl_env.Set(k, v)
+    }
+    repl_env.Set("eval", func(a []MalType) (MalType, error) {
+        return EVAL(a[0], repl_env) })
+    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) \")\")))))")
+
+    // called with mal script to load and eval
+    if len(os.Args) > 1 {
+        args := make([]MalType, 0, len(os.Args)-2)
+        for _,a := range os.Args[2:] {
+            args = append(args, a)
+        }
+        repl_env.Set("*ARGV*", List{args})
+        rep("(load-file \"" + os.Args[1] + "\")")
+        os.Exit(0)
+    }
+
+    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)
+    }
+}