-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreader.go
134 lines (120 loc) · 3.38 KB
/
reader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package malarkey
import (
"regexp"
"strconv"
"strings"
)
var tokenRegex = regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'` + "`" + `~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"` + "`" + `,;)]*)`)
// Reader reads tokens.
type Reader struct {
Tokens []string
Position int
}
// Next returns the next token and advances the reader.
func (r *Reader) Next() string {
if r.Position >= len(r.Tokens) {
return ""
}
r.Position++
return r.Tokens[r.Position-1]
}
// Peek returns the next token without advancing the reader.
func (r *Reader) Peek() string {
if r.Position >= len(r.Tokens) {
return ""
}
return r.Tokens[r.Position]
}
// Tokenize splits a input text into tokens.
func Tokenize(input string) []string {
matches := tokenRegex.FindAllStringSubmatch(input, -1)
var out []string
for _, match := range matches {
// note: regex does not drop leading whitespaces and commas. trim until no change
var cur string
trimmed := match[0]
for trimmed != cur {
cur = trimmed
trimmed = strings.Trim(strings.TrimSpace(cur), ",")
}
if cur == "" || strings.HasPrefix(cur, ";") {
continue
}
out = append(out, cur)
}
return out
}
// Read parses input text into an AST.
func Read(input string) Value {
reader := &Reader{Tokens: Tokenize(input)}
s := readForm(reader)
if reader.Peek() != "" {
panic("invalid trailing tokens")
}
return s
}
func readCollection(reader *Reader, peeked string) Value {
stopToken := map[string]string{"(": ")", "[": "]", "{": "}"}[peeked]
seqType := map[string]string{"(": "list", "[": "vector", "{": "hash-map"}[peeked]
reader.Next()
var elements []Value
for reader.Peek() != stopToken {
elements = append(elements, readForm(reader))
}
reader.Next()
if seqType == "hash-map" {
kv := map[string]Value{}
for i := 0; i < len(elements); i += 2 {
kv[elements[i].Val.(string)] = elements[i+1]
}
return Value{Type: "hash-map", Val: kv}
}
return Value{Type: seqType, Val: elements}
}
// only currently supporting integers and symbols
func readAtom(reader *Reader) Value {
token := reader.Next()
if token == "" {
panic("expected atom")
}
if i, err := strconv.ParseInt(token, 10, 0); err == nil {
return Value{Type: "integer", Val: i}
}
if f, err := strconv.ParseFloat(token, 64); err == nil {
return Value{Type: "float", Val: f}
}
if strings.HasPrefix(token, "\"") {
str := token[1 : len(token)-1]
str = strings.Replace(str, "\\\"", "\"", -1)
str = strings.Replace(str, "\\n", "\n", -1)
str = strings.Replace(str, "\\\\", "\\", -1)
return Value{Type: "string", Val: str}
}
if strings.HasPrefix(token, ":") {
return Value{Type: "keyword", Val: token}
}
switch token {
case "true":
return Value{Type: "boolean", Val: true}
case "false":
return Value{Type: "boolean", Val: false}
case "nil":
return Value{Type: "nil", Val: nil}
default:
return Value{Type: "symbol", Val: token}
}
}
// readForm parses the next form from the reader.
// Currently only supporting lists and atoms.
func readForm(reader *Reader) Value {
peekToken := reader.Peek()
switch peekToken {
case "@", "'", "`", "~", "~@": // reader macros
syms := map[string]string{"@": "deref", "'": "quote", "`": "quasiquote", "~": "unquote", "~@": "splice-unquote"}
reader.Next()
return Value{Type: "list", Val: []Value{{Type: "symbol", Val: syms[peekToken]}, readForm(reader)}}
case "(", "[", "{":
return readCollection(reader, peekToken)
}
return readAtom(reader)
}