aboutsummaryrefslogtreecommitdiff
path: root/go/src/reader
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2015-03-02 21:33:10 -0600
committerJoel Martin <github@martintribe.org>2015-03-02 21:33:10 -0600
commit835fb7d8b06e2b44792a97ac89994658bf6d00af (patch)
tree578f67726ab9e3ce5fcbc50220e9761a66c5ddf1 /go/src/reader
parent6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (diff)
parent8a98ef9a3f3a6b6d05d02dc305a0c886c907e0f3 (diff)
downloadmal-835fb7d8b06e2b44792a97ac89994658bf6d00af.tar.gz
mal-835fb7d8b06e2b44792a97ac89994658bf6d00af.zip
Merge branch 'master' into gh-pages
Conflicts: .gitignore
Diffstat (limited to 'go/src/reader')
-rw-r--r--go/src/reader/reader.go162
1 files changed, 162 insertions, 0 deletions
diff --git a/go/src/reader/reader.go b/go/src/reader/reader.go
new file mode 100644
index 0000000..9cacb8b
--- /dev/null
+++ b/go/src/reader/reader.go
@@ -0,0 +1,162 @@
+package reader
+
+import (
+ "errors"
+ "regexp"
+ "strconv"
+ "strings"
+ //"fmt"
+)
+
+import (
+ . "types"
+)
+
+type Reader interface {
+ next() *string
+ peek() *string
+}
+
+type TokenReader struct {
+ tokens []string
+ position int
+}
+
+func (tr *TokenReader) next() *string {
+ if tr.position >= len(tr.tokens) { return nil }
+ token := tr.tokens[tr.position]
+ tr.position = tr.position + 1
+ return &token
+}
+
+func (tr *TokenReader) peek() *string {
+ if tr.position >= len(tr.tokens) { return nil }
+ return &tr.tokens[tr.position]
+}
+
+
+
+func tokenize (str string) []string {
+ results := make([]string, 0, 1)
+ // 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])
+ }
+ return results
+}
+
+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 {
+ var i int
+ var e error
+ if i, e = strconv.Atoi(*token); e != nil {
+ return nil, errors.New("number parse error")
+ }
+ return i, nil
+ } else if (*token)[0] == '"' {
+ str := (*token)[1:len(*token)-1]
+ return strings.Replace(
+ strings.Replace(str, `\"`, `"`, -1),
+ `\n`, "\n", -1), nil
+ } else if (*token)[0] == ':' {
+ return NewKeyword((*token)[1:len(*token)])
+ } else if *token == "nil" {
+ return nil, nil
+ } else if *token == "true" {
+ return true, nil
+ } else if *token == "false" {
+ return false, nil
+ } else {
+ return Symbol{*token}, nil
+ }
+ return token, nil
+}
+
+func read_list(rdr Reader, start string, end string) (MalType, error) {
+ token := rdr.next()
+ if token == nil { return nil, errors.New("read_list underflow") }
+ if *token != start {
+ return nil, errors.New("expected '" + start + "'")
+ }
+
+ ast_list := []MalType{}
+ token = rdr.peek()
+ for ; true ; token = rdr.peek() {
+ if token == nil { return nil, errors.New("exepected '" + end + "', got EOF") }
+ if *token == end { break }
+ f, e := read_form(rdr)
+ if e != nil { return nil, e }
+ ast_list = append(ast_list, f)
+ }
+ rdr.next()
+ return List{ast_list,nil}, nil
+}
+
+func read_vector(rdr Reader) (MalType, error) {
+ lst, e := read_list(rdr, "[", "]")
+ if e != nil { return nil, e }
+ vec := Vector{lst.(List).Val,nil}
+ return vec, nil
+}
+
+func read_hash_map(rdr Reader) (MalType, error) {
+ mal_lst, e := read_list(rdr, "{", "}")
+ if e != nil { return nil, e }
+ return NewHashMap(mal_lst)
+}
+
+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}, nil
+ case "`": rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"quasiquote"}, form},nil}, nil
+ case `~`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"unquote"}, form},nil}, nil
+ case `~@`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"splice-unquote"}, form},nil}, nil
+ case `^`: rdr.next();
+ meta, e := read_form(rdr); if e != nil { return nil, e }
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"with-meta"}, form,meta},nil}, nil
+ case `@`: rdr.next();
+ form, e := read_form(rdr); if e != nil { return nil, e }
+ return List{[]MalType{Symbol{"deref"}, form},nil}, nil
+
+ // list
+ case ")": return nil, errors.New("unexpected ')'")
+ case "(": return read_list(rdr, "(", ")")
+
+ // vector
+ case "]": return nil, errors.New("unexpected ']'")
+ case "[": return read_vector(rdr)
+
+ // hash-map
+ case "}": return nil, errors.New("unexpected '}'")
+ case "{": return read_hash_map(rdr)
+ default: return read_atom(rdr)
+ }
+ return read_atom(rdr)
+}
+
+func Read_str(str string) (MalType, error) {
+ var tokens = tokenize(str);
+ if len(tokens) == 0 {
+ return nil, errors.New("<empty line>")
+ }
+
+ return read_form(&TokenReader{tokens: tokens, position: 0})
+}