DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / chuck / reader.ck
CommitLineData
e83d6df7
VS
1public 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}