diff options
| author | Joel Martin <github@martintribe.org> | 2015-03-02 21:33:10 -0600 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2015-03-02 21:33:10 -0600 |
| commit | 835fb7d8b06e2b44792a97ac89994658bf6d00af (patch) | |
| tree | 578f67726ab9e3ce5fcbc50220e9761a66c5ddf1 /go/src/reader | |
| parent | 6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (diff) | |
| parent | 8a98ef9a3f3a6b6d05d02dc305a0c886c907e0f3 (diff) | |
| download | mal-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.go | 162 |
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}) +} |
