Commit | Line | Data |
---|---|---|
034e82ad DM |
1 | class Reader { |
2 | const tokens List<string> | |
3 | var position = 0 | |
4 | ||
5 | def peek string { | |
6 | if position >= tokens.count { | |
7 | return null | |
8 | } | |
9 | return tokens[position] | |
10 | } | |
11 | ||
12 | def next string { | |
13 | const token = peek | |
14 | position++ | |
15 | return token | |
16 | } | |
17 | } | |
18 | ||
19 | def tokenize(str string) List<string> { | |
20 | var re = RegExp.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)", "g") | |
21 | var tokens List<string> = [] | |
22 | var match string | |
23 | while (match = re.exec(str)[1]) != "" { | |
24 | if match[0] == ';' { | |
25 | continue | |
26 | } | |
27 | tokens.append(match) | |
28 | } | |
29 | return tokens | |
30 | } | |
31 | ||
32 | def unescape(s string) string { | |
33 | return s.replaceAll("\\\"", "\"").replaceAll("\\n", "\n").replaceAll("\\\\", "\\") | |
34 | } | |
35 | ||
36 | def read_atom(rdr Reader) MalVal { | |
37 | const token = rdr.peek | |
38 | if token == "nil" { | |
39 | rdr.next | |
40 | return gNil | |
41 | } | |
42 | if token == "true" { | |
43 | rdr.next | |
44 | return gTrue | |
45 | } | |
46 | if token == "false" { | |
47 | rdr.next | |
48 | return gFalse | |
49 | } | |
50 | switch token[0] { | |
51 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' { return MalNumber.new(stringToInt(rdr.next)) } | |
52 | case '-' { | |
53 | if token.count <= 1 { return MalSymbol.new(rdr.next) } | |
54 | switch token[1] { | |
55 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' { return MalNumber.new(stringToInt(rdr.next)) } | |
56 | default { return MalSymbol.new(rdr.next) } | |
57 | } | |
58 | } | |
59 | case '"' { | |
60 | const s = rdr.next | |
61 | if s[s.count - 1] == '"' { | |
62 | return MalString.new(unescape(s.slice(1, s.count - 1))) | |
63 | } else { | |
64 | throw MalError.new("expected '\"', got EOF") | |
65 | } | |
66 | } | |
67 | case ':' { return MalKeyword.new(rdr.next.slice(1)) } | |
68 | default { return MalSymbol.new(rdr.next) } | |
69 | } | |
70 | } | |
71 | ||
72 | def read_sequence(rdr Reader, open string, close string) List<MalVal> { | |
73 | if rdr.next != open { | |
74 | throw MalError.new("expected '" + open + "'") | |
75 | } | |
76 | var token string | |
77 | var items List<MalVal> = [] | |
78 | while (token = rdr.peek) != close { | |
79 | if token == null { | |
80 | throw MalError.new("expected '" + close + "', got EOF") | |
81 | } | |
82 | items.append(read_form(rdr)) | |
83 | } | |
84 | rdr.next # consume the close paren/bracket/brace | |
85 | return items | |
86 | } | |
87 | ||
88 | def read_list(rdr Reader) MalList { | |
89 | return MalList.new(read_sequence(rdr, "(", ")")) | |
90 | } | |
91 | ||
92 | def read_vector(rdr Reader) MalVector { | |
93 | return MalVector.new(read_sequence(rdr, "[", "]")) | |
94 | } | |
95 | ||
96 | def read_hash_map(rdr Reader) MalHashMap { | |
97 | return MalHashMap.fromList(read_sequence(rdr, "{", "}")) | |
98 | } | |
99 | ||
100 | def reader_macro(rdr Reader, symbol_name string) MalVal { | |
101 | rdr.next | |
102 | return MalList.new([MalSymbol.new(symbol_name), read_form(rdr)]) | |
103 | } | |
104 | ||
105 | def read_form(rdr Reader) MalVal { | |
106 | switch rdr.peek[0] { | |
107 | case '\'' { return reader_macro(rdr, "quote") } | |
108 | case '`' { return reader_macro(rdr, "quasiquote") } | |
109 | case '~' { | |
110 | if rdr.peek == "~" { return reader_macro(rdr, "unquote") } | |
111 | else if rdr.peek == "~@" { return reader_macro(rdr, "splice-unquote") } | |
112 | else { return read_atom(rdr) } | |
113 | } | |
114 | case '^' { | |
115 | rdr.next | |
116 | const meta = read_form(rdr) | |
117 | return MalList.new([MalSymbol.new("with-meta"), read_form(rdr), meta]) | |
118 | } | |
119 | case '@' { return reader_macro(rdr, "deref") } | |
120 | case ')' { throw MalError.new("unexpected ')'") } | |
121 | case '(' { return read_list(rdr) } | |
122 | case ']' { throw MalError.new("unexpected ']'") } | |
123 | case '[' { return read_vector(rdr) } | |
124 | case '}' { throw MalError.new("unexpected '}'") } | |
125 | case '{' { return read_hash_map(rdr) } | |
126 | default { return read_atom(rdr) } | |
127 | } | |
128 | } | |
129 | ||
130 | def read_str(str string) MalVal { | |
131 | const tokens = tokenize(str) | |
132 | if tokens.isEmpty { return null } | |
133 | var rdr = Reader.new(tokens) | |
134 | return read_form(rdr) | |
135 | } | |
136 | ||
137 | @import { | |
138 | const RegExp dynamic | |
139 | } |