go: add step7_quote
[jackhill/mal.git] / go / src / reader / reader.go
1 package reader
2
3 import (
4 "errors"
5 "regexp"
6 "strconv"
7 "strings"
8 //"fmt"
9 )
10
11 import (
12 . "types"
13 )
14
15 type Reader interface {
16 next() *string
17 peek() *string
18 }
19
20 type TokenReader struct {
21 tokens []string
22 position int
23 }
24
25 func (tr *TokenReader) next() *string {
26 if tr.position >= len(tr.tokens) { return nil }
27 token := tr.tokens[tr.position]
28 tr.position = tr.position + 1
29 return &token
30 }
31
32 func (tr *TokenReader) peek() *string {
33 if tr.position >= len(tr.tokens) { return nil }
34 return &tr.tokens[tr.position]
35 }
36
37
38
39 func tokenize (str string) []string {
40 results := make([]string, 0, 1)
41 // Work around lack of quoting in backtick
42 re := regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'` + "`" +
43 `~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` + "`" +
44 `,;)]*)`)
45 for _, group := range re.FindAllStringSubmatch(str, -1) {
46 if (group[1] == "") || (group[1][0] == ';') { continue }
47 results = append(results, group[1])
48 }
49 return results
50 }
51
52 func read_atom(rdr Reader) (MalType, error) {
53 token := rdr.next()
54 if token == nil { return nil, errors.New("read_atom underflow") }
55 if match, _ := regexp.MatchString(`^-?[0-9]+$`, *token); match {
56 var i int
57 var e error
58 if i, e = strconv.Atoi(*token); e != nil {
59 return nil, errors.New("number parse error")
60 }
61 return i, nil
62 } else if (*token)[0] == '"' {
63 str := (*token)[1:len(*token)-1]
64 return strings.Replace(
65 strings.Replace(str, `\"`, `"`, -1),
66 `\n`, "\n", -1), nil
67 } else if *token == "nil" {
68 return nil, nil
69 } else if *token == "true" {
70 return true, nil
71 } else if *token == "false" {
72 return false, nil
73 } else {
74 return Symbol{*token}, nil
75 }
76 return token, nil
77 }
78
79 func read_list(rdr Reader, start string, end string) (MalType, error) {
80 token := rdr.next()
81 if token == nil { return nil, errors.New("read_list underflow") }
82
83 ast_list := []MalType{}
84 if *token != start {
85 return nil, errors.New("expected '" + start + "'")
86 }
87 token = rdr.peek()
88 for ; true ; token = rdr.peek() {
89 if token == nil { return nil, errors.New("exepected '" + end + "', got EOF") }
90 if *token == end { break }
91 f, e := read_form(rdr)
92 if e != nil { return nil, e }
93 ast_list = append(ast_list, f)
94 }
95 rdr.next()
96 return List{ast_list}, nil
97 }
98
99 func read_vector(rdr Reader) (MalType, error) {
100 lst, e := read_list(rdr, "[", "]")
101 if e != nil { return nil, e }
102 vec := Vector{lst.(List).Val}
103 return vec, nil
104 }
105
106 func read_hash_map(rdr Reader) (MalType, error) {
107 mal_lst, e := read_list(rdr, "{", "}")
108 lst := mal_lst.(List).Val
109 if e != nil { return nil, e }
110 if len(lst) % 2 == 1 {
111 return nil, errors.New("Odd number of hash map arguments")
112 }
113 m := map[string]MalType{}
114 for i := 0; i < len(lst); i+=2 {
115 str, ok := lst[i].(string)
116 if !ok {
117 return nil, errors.New("expected hash-map key string")
118 }
119 m[str] = lst[i+1]
120 }
121 return m, nil
122 }
123
124 func read_form(rdr Reader) (MalType, error) {
125 token := rdr.peek()
126 if token == nil { return nil, errors.New("read_form underflow") }
127 switch (*token) {
128
129 case `'`: rdr.next();
130 form, e := read_form(rdr); if e != nil { return nil, e }
131 return List{[]MalType{Symbol{"quote"}, form}}, nil
132 case "`": rdr.next();
133 form, e := read_form(rdr); if e != nil { return nil, e }
134 return List{[]MalType{Symbol{"quasiquote"}, form}}, nil
135 case `~`: rdr.next();
136 form, e := read_form(rdr); if e != nil { return nil, e }
137 return List{[]MalType{Symbol{"unquote"}, form}}, nil
138 case `~@`: rdr.next();
139 form, e := read_form(rdr); if e != nil { return nil, e }
140 return List{[]MalType{Symbol{"splice-unquote"}, form}}, nil
141
142 // list
143 case ")": return nil, errors.New("unexpected ')'")
144 case "(": return read_list(rdr, "(", ")")
145
146 // vector
147 case "]": return nil, errors.New("unexpected ']'")
148 case "[": return read_vector(rdr)
149
150 // hash-map
151 case "}": return nil, errors.New("unexpected '}'")
152 case "{": return read_hash_map(rdr)
153 default: return read_atom(rdr)
154 }
155 return read_atom(rdr)
156 }
157
158 func Read_str(str string) (MalType, error) {
159 var tokens = tokenize(str);
160 if len(tokens) == 0 {
161 return nil, errors.New("<empty line>")
162 }
163
164 return read_form(&TokenReader{tokens: tokens, position: 0})
165 }