Commit | Line | Data |
---|---|---|
e83d6df7 VS |
1 | public class Reader |
2 | { | |
3 | 0 => int position; | |
4 | string tokens[]; | |
5 | ||
6 | fun string peek() | |
7 | { | |
8 | return tokens[position]; | |
9 | } | |
10 | ||
11 | fun string next() | |
12 | { | |
13 | return tokens[position++]; | |
14 | } | |
15 | ||
16 | fun static string[] tokenizer(string input) | |
17 | { | |
65634b37 | 18 | "^[ \n,]*(~@|[][{}()'`~^@]|\"(\\\\.|[^\\\"])*\"|;[^\n]*|[^][ \n{}()'`~@,;\"]*)" => string tokenRe; |
aa0ac94f | 19 | "^([ \n,]*|;[^\n]*)$" => string blankRe; |
e83d6df7 VS |
20 | |
21 | string tokens[0]; | |
22 | ||
23 | while( true ) | |
24 | { | |
25 | string matches[1]; | |
26 | RegEx.match(tokenRe, input, matches); | |
27 | matches[1] => string token; | |
28 | ||
29 | if( token.length() == 0 && !RegEx.match(blankRe, input) ) | |
30 | { | |
31 | tokens << input; | |
32 | break; | |
33 | } | |
34 | ||
35 | if( !RegEx.match(blankRe, token) ) | |
36 | { | |
37 | tokens << token; | |
38 | } | |
39 | ||
40 | matches[0].length() => int tokenStart; | |
41 | String.slice(input, tokenStart) => input; | |
42 | ||
43 | if( input.length() == 0 ) | |
44 | { | |
45 | break; | |
46 | } | |
47 | } | |
48 | ||
49 | return tokens; | |
50 | } | |
51 | ||
52 | fun static MalObject read_str(string input) | |
53 | { | |
54 | Reader reader; | |
55 | tokenizer(input) @=> reader.tokens; | |
56 | ||
57 | if( reader.tokens.size() == 0 ) | |
58 | { | |
98c1ecf2 | 59 | return MalError.create(MalString.create("empty input")); |
e83d6df7 VS |
60 | } |
61 | else | |
62 | { | |
63 | return read_form(reader); | |
64 | } | |
65 | } | |
66 | ||
67 | fun static MalObject read_form(Reader reader) | |
68 | { | |
69 | reader.peek() => string token; | |
70 | if( token == "(" ) | |
71 | { | |
72 | return read_list(reader, "(", ")"); | |
73 | } | |
74 | else if( token == "[" ) | |
75 | { | |
76 | return read_list(reader, "[", "]"); | |
77 | } | |
78 | else if( token == "{" ) | |
79 | { | |
80 | return read_list(reader, "{", "}"); | |
81 | } | |
82 | else if( token == ")" || token == "]" || token == "}" ) | |
83 | { | |
98c1ecf2 | 84 | return MalError.create(MalString.create("unexpected '" + token + "'")); |
e83d6df7 VS |
85 | } |
86 | else if( token == "'" ) | |
87 | { | |
88 | return read_simple_reader_macro(reader, "quote"); | |
89 | } | |
90 | else if( token == "`" ) | |
91 | { | |
92 | return read_simple_reader_macro(reader, "quasiquote"); | |
93 | } | |
94 | else if( token == "~" ) | |
95 | { | |
96 | return read_simple_reader_macro(reader, "unquote"); | |
97 | } | |
98 | else if( token == "~@" ) | |
99 | { | |
100 | return read_simple_reader_macro(reader, "splice-unquote"); | |
101 | } | |
102 | else if( token == "@" ) | |
103 | { | |
104 | return read_simple_reader_macro(reader, "deref"); | |
105 | } | |
106 | else if( token == "^" ) | |
107 | { | |
108 | return read_meta_reader_macro(reader); | |
109 | } | |
110 | else | |
111 | { | |
112 | return read_atom(reader); | |
113 | } | |
114 | } | |
115 | ||
116 | fun static MalObject read_list(Reader reader, string start, string end) | |
117 | { | |
118 | MalObject items[0]; | |
119 | ||
120 | reader.next(); // discard list start token | |
121 | ||
122 | while( true ) | |
123 | { | |
124 | // HACK: avoid checking for reader.peek() returning null | |
125 | // (as doing that directly isn't possible and too | |
126 | // bothersome to do indirectly) | |
127 | if( reader.position == reader.tokens.size() ) | |
128 | { | |
98c1ecf2 | 129 | return MalError.create(MalString.create("expected '" + end + "', got EOF")); |
e83d6df7 VS |
130 | } |
131 | ||
132 | if( reader.peek() == end ) | |
133 | { | |
134 | break; | |
135 | } | |
136 | ||
137 | read_form(reader) @=> MalObject item; | |
138 | ||
139 | if( item.type == "error" ) | |
140 | { | |
141 | return item; | |
142 | } | |
143 | else | |
144 | { | |
145 | items << item; | |
146 | } | |
147 | } | |
148 | ||
149 | reader.next(); // discard list end token | |
150 | ||
151 | if( start == "(" ) | |
152 | { | |
153 | return MalList.create(items); | |
154 | } | |
155 | else if( start == "[" ) | |
156 | { | |
157 | return MalVector.create(items); | |
158 | } | |
159 | else if( start == "{" ) | |
160 | { | |
161 | return MalHashMap.create(items); | |
162 | } | |
163 | } | |
164 | ||
165 | fun static MalObject read_atom(Reader reader) | |
166 | { | |
167 | "^[+-]?[0-9]+$" => string intRe; | |
168 | "^\"(\\\\.|[^\\\"])*\"$" => string stringRe; | |
169 | ||
170 | reader.next() => string token; | |
171 | ||
172 | if( token == "true" ) | |
173 | { | |
f823ec25 | 174 | return Constants.TRUE; |
e83d6df7 VS |
175 | } |
176 | else if( token == "false" ) | |
177 | { | |
f823ec25 | 178 | return Constants.FALSE; |
e83d6df7 VS |
179 | } |
180 | else if( token == "nil" ) | |
181 | { | |
f823ec25 | 182 | return Constants.NIL; |
e83d6df7 VS |
183 | } |
184 | else if( RegEx.match(intRe, token) ) | |
185 | { | |
186 | return MalInt.create(Std.atoi(token)); | |
187 | } | |
188 | else if( token.substring(0, 1) == "\"" ) | |
189 | { | |
190 | if( RegEx.match(stringRe, token) ) | |
191 | { | |
192 | return MalString.create(String.parse(token)); | |
193 | } | |
194 | else | |
195 | { | |
98c1ecf2 | 196 | return MalError.create(MalString.create("expected '\"', got EOF")); |
e83d6df7 VS |
197 | } |
198 | } | |
199 | else if( token.substring(0, 1) == ":" ) | |
200 | { | |
201 | return MalKeyword.create(String.slice(token, 1)); | |
202 | } | |
203 | else | |
204 | { | |
205 | return MalSymbol.create(token); | |
206 | } | |
207 | } | |
208 | ||
209 | fun static MalObject read_simple_reader_macro(Reader reader, string symbol) | |
210 | { | |
211 | reader.next(); // discard reader macro token | |
212 | ||
213 | read_form(reader) @=> MalObject form; | |
214 | if( form.type == "error" ) | |
215 | { | |
216 | return form; | |
217 | } | |
218 | ||
219 | return MalList.create([MalSymbol.create(symbol), form]); | |
220 | } | |
221 | ||
222 | fun static MalObject read_meta_reader_macro(Reader reader) | |
223 | { | |
224 | reader.next(); // discard reader macro token | |
225 | ||
226 | read_form(reader) @=> MalObject meta; | |
227 | if( meta.type == "error" ) | |
228 | { | |
229 | return meta; | |
230 | } | |
231 | ||
232 | read_form(reader) @=> MalObject form; | |
233 | if( form.type == "error" ) | |
234 | { | |
235 | return meta; | |
236 | } | |
237 | ||
238 | return MalList.create([MalSymbol.create("with-meta"), form, meta]); | |
239 | } | |
240 | } |