Commit | Line | Data |
---|---|---|
4d8cfe7e | 1 | |
894f5ce8 JB |
2 | { |
3 | zip, map, apply, and-list, join, Obj, concat, all, | |
4 | pairs-to-obj, obj-to-pairs, reject, keys, values, | |
4d8cfe7e | 5 | difference, empty, reverse, chars |
894f5ce8 | 6 | } = require 'prelude-ls' |
3181c695 | 7 | {pr_str} = require './printer' |
894f5ce8 | 8 | {read_str, list-to-map, map-keyword, keyword-prefix} = require './reader' |
25bb14c9 | 9 | fs = require 'fs' |
4d8cfe7e | 10 | {readline} = require './node_readline' |
3181c695 JB |
11 | |
12 | ||
13 | export runtime-error = (msg) -> throw new Error msg | |
14 | ||
86e32f4d JB |
15 | export unpack-tco = (ast) -> |
16 | if ast.type == \tco | |
17 | then ast.eval! | |
18 | else ast | |
19 | ||
3181c695 | 20 | fn = (body) -> {type: \function, value: body} |
2ff2d84b | 21 | const-nil = -> {type: \const, value: \nil} |
3181c695 JB |
22 | const-int = (int) -> {type: \int, value: int} |
23 | const-bool = (bool) -> {type: \const, value: if bool then \true else \false} | |
24 | const-str = (str) -> {type: \string, value: str} | |
25 | ||
26 | list-or-vector = ({type}) -> type in [\list \vector] | |
27 | ||
894f5ce8 JB |
28 | are-lists-equal = (equals-fn, a, b) -> |
29 | if a.length != b.length then false | |
30 | else zip a, b |> map (apply equals-fn) |> and-list | |
31 | ||
3181c695 | 32 | deep-equals = (a, b) -> |
894f5ce8 JB |
33 | if (list-or-vector a) and (list-or-vector b) then |
34 | are-lists-equal deep-equals, a.value, b.value | |
35 | else if a.type == \map and b.type == \map then | |
36 | a-keys = keys a.value | |
37 | b-keys = keys b.value | |
38 | if a-keys.length == b-keys.length and \ | |
39 | empty (difference a-keys, b-keys) | |
40 | #if are-lists-equal (==), a-keys, b-keys | |
41 | a-keys |> map (key) -> [a.value[key], b.value[key]] | |
42 | |> map (apply deep-equals) | |
43 | |> and-list | |
44 | else false | |
45 | else if a.type != b.type then false | |
46 | else a.value == b.value | |
3181c695 JB |
47 | |
48 | ||
a650ae5b JB |
49 | check-param = (name, idx, test, expected, actual) -> |
50 | if not test | |
51 | runtime-error "'#{name}' expected parameter #{idx} | |
52 | to be #{expected}, got #{actual}" | |
53 | ||
54 | ||
55 | check-type = (name, idx, expected, actual) -> | |
56 | check-param name, idx, expected == actual, expected, actual | |
25bb14c9 JB |
57 | |
58 | ||
3181c695 JB |
59 | export ns = do |
60 | '+': fn (a, b) -> const-int a.value + b.value | |
61 | '-': fn (a, b) -> const-int a.value - b.value | |
62 | '*': fn (a, b) -> const-int a.value * b.value | |
63 | '/': fn (a, b) -> const-int parseInt (a.value / b.value) | |
64 | ||
65 | 'list': fn (...list) -> {type: \list, value: list} | |
66 | 'list?': fn (param) -> const-bool param.type == \list | |
67 | ||
2ff2d84b JB |
68 | 'empty?': fn ({type, value}) -> |
69 | switch type | |
70 | | \const => | |
71 | if value == \nil | |
72 | then const-bool true | |
73 | else runtime-error "'empty?' is not supported on #{value}" | |
74 | | \list, \vector => | |
75 | const-bool value.length == 0 | |
76 | | \map => | |
77 | const-bool Obj.empty value | |
78 | | otherwise => | |
79 | runtime-error "'empty?' is not supported on type #{type}" | |
80 | ||
81 | 'count': fn ({type, value}) -> | |
82 | switch type | |
83 | | \const => | |
84 | if value == \nil | |
85 | then const-int 0 | |
86 | else runtime-error "'count' is not supported on #{value}" | |
87 | | \list, \vector => | |
88 | const-int value.length | |
89 | | \map => | |
90 | value |> Obj.keys |> (.length) |> const-int | |
91 | | otherwise => | |
92 | runtime-error "'count' is not supported on type #{type}" | |
3181c695 JB |
93 | |
94 | '=': fn (a, b) -> const-bool (deep-equals a, b) | |
95 | '<': fn (a, b) -> const-bool a.value < b.value | |
96 | '>': fn (a, b) -> const-bool a.value > b.value | |
97 | '<=': fn (a, b) -> const-bool a.value <= b.value | |
98 | '>=': fn (a, b) -> const-bool a.value >= b.value | |
99 | ||
2ff2d84b JB |
100 | 'pr-str': fn (...params) -> |
101 | params |> map (p) -> pr_str p, print_readably=true | |
102 | |> join ' ' | |
103 | |> const-str | |
104 | ||
105 | 'str': fn (...params) -> | |
106 | params |> map (p) -> pr_str p, print_readably=false | |
107 | |> join '' | |
108 | |> const-str | |
109 | ||
110 | 'prn': fn (...params) -> | |
111 | params |> map (p) -> pr_str p, print_readably=true | |
112 | |> join ' ' | |
113 | |> console.log | |
114 | |> const-nil | |
115 | ||
116 | 'println': fn (...params) -> | |
117 | params |> map (p) -> pr_str p, print_readbly=false | |
118 | |> join ' ' | |
119 | |> console.log | |
120 | |> const-nil | |
25bb14c9 JB |
121 | |
122 | 'read-string': fn ({type, value}) -> | |
a650ae5b | 123 | check-type 'read-string', 0, \string, type |
25bb14c9 JB |
124 | read_str value |
125 | ||
126 | 'slurp': fn (filename) -> | |
127 | if filename.type != \string | |
128 | runtime-error "'slurp' expected the first parameter | |
129 | to be a string, got a #{filename.type}" | |
130 | ||
131 | const-str <| fs.readFileSync filename.value, 'utf8' | |
132 | ||
133 | 'atom': fn (value) -> {type: \atom, value: value} | |
134 | 'atom?': fn (atom) -> const-bool atom.type == \atom | |
135 | 'deref': fn (atom) -> | |
a650ae5b | 136 | check-type 'deref', 0, \atom, atom.type |
25bb14c9 JB |
137 | atom.value |
138 | ||
139 | 'reset!': fn (atom, value) -> | |
a650ae5b | 140 | check-type 'reset!', 0, \atom, atom.type |
25bb14c9 JB |
141 | atom.value = value |
142 | ||
143 | 'swap!': fn (atom, fn, ...args) -> | |
a650ae5b | 144 | check-type 'swap!', 0, \atom, atom.type |
25bb14c9 JB |
145 | if fn.type != \function |
146 | runtime-error "'swap!' expected the second parameter | |
147 | to be a function, got a #{fn.type}" | |
148 | ||
86e32f4d | 149 | atom.value = unpack-tco (fn.value.apply @, [atom.value] ++ args) |
a650ae5b JB |
150 | |
151 | 'cons': fn (value, list) -> | |
152 | check-param 'cons', 1, (list-or-vector list), | |
153 | 'list or vector', list.type | |
154 | ||
155 | {type: \list, value: [value] ++ list.value} | |
156 | ||
157 | 'concat': fn (...params) -> | |
158 | if not all list-or-vector, params | |
159 | runtime-error "'concat' expected all parameters to be a list or vector" | |
160 | ||
161 | {type: \list, value: params |> map (.value) |> concat} | |
19677091 | 162 | |
fbfe6784 NB |
163 | 'vec': fn (sequence) -> |
164 | check-param 'vec', 0, (list-or-vector sequence), | |
165 | 'list or vector', sequence.type | |
166 | ||
167 | {type: \vector, value: sequence.value} | |
168 | ||
19677091 JB |
169 | 'nth': fn (list, index) -> |
170 | check-param 'nth', 0, (list-or-vector list), | |
171 | 'list or vector', list.type | |
172 | check-param 'nth', 1, index.type == \int, | |
173 | 'int', index.type | |
174 | ||
175 | if index.value < 0 or index.value >= list.value.length | |
176 | runtime-error 'list index out of bounds' | |
177 | ||
178 | list.value[index.value] | |
179 | ||
180 | 'first': fn (list) -> | |
b145558e JB |
181 | if list.type == \const and list.value == \nil |
182 | return const-nil! | |
183 | ||
19677091 JB |
184 | check-param 'first', 0, (list-or-vector list), |
185 | 'list or vector', list.type | |
186 | ||
187 | if list.value.length == 0 | |
188 | then const-nil! | |
189 | else list.value[0] | |
190 | ||
191 | 'rest': fn (list) -> | |
b145558e JB |
192 | if list.type == \const and list.value == \nil |
193 | return {type: \list, value: []} | |
194 | ||
19677091 JB |
195 | check-param 'rest', 0, (list-or-vector list), |
196 | 'list or vector', list.type | |
197 | ||
198 | {type: \list, value: list.value.slice 1} | |
65164fe2 JB |
199 | |
200 | 'throw': fn (value) -> throw value | |
201 | ||
202 | 'apply': fn (fn, ...params, list) -> | |
203 | check-type 'apply', 0, \function, fn.type | |
204 | if not list then runtime-error "apply expected at least two parameters" | |
205 | check-param 'apply', params.length+1, (list-or-vector list), | |
206 | 'list or vector', list.type | |
207 | ||
208 | unpack-tco fn.value.apply @, params ++ list.value | |
209 | ||
210 | 'map': fn (fn, list) -> | |
211 | check-type 'map', 0, \function, fn.type | |
212 | check-param 'map', 1, (list-or-vector list), | |
213 | 'list or vector', list.type | |
214 | ||
215 | mapped-list = list.value |> map (value) -> | |
216 | unpack-tco fn.value.apply @, [value] | |
217 | ||
218 | {type: \list, value: mapped-list} | |
219 | ||
220 | 'nil?': fn (ast) -> const-bool (ast.type == \const and ast.value == \nil) | |
221 | 'true?': fn (ast) -> const-bool (ast.type == \const and ast.value == \true) | |
222 | 'false?': fn (ast) -> const-bool (ast.type == \const and ast.value == \false) | |
223 | 'symbol?': fn (ast) -> const-bool ast.type == \symbol | |
224 | ||
225 | 'symbol': fn (str) -> | |
226 | check-type 'symbol', 0, \string, str.type | |
227 | {type: \symbol, value: str.value} | |
228 | ||
229 | 'keyword': fn (str) -> | |
230 | check-type 'keyword', 0, \string, str.type | |
894f5ce8 | 231 | {type: \keyword, value: ':' + str.value} |
65164fe2 JB |
232 | |
233 | 'keyword?': fn (ast) -> const-bool ast.type == \keyword | |
894f5ce8 | 234 | |
9968eecb DM |
235 | 'number?': fn (ast) -> const-bool ast.type == \int |
236 | 'fn?': fn (ast) -> const-bool (ast.type == \function and not ast.is_macro) | |
237 | 'macro?': fn (ast) -> const-bool (ast.type == \function and ast.is_macro) | |
238 | ||
894f5ce8 JB |
239 | 'vector': fn (...params) -> {type: \vector, value: params} |
240 | 'vector?': fn (ast) -> const-bool ast.type == \vector | |
241 | ||
242 | 'hash-map': fn (...params) -> list-to-map params | |
243 | ||
244 | 'map?': fn (ast) -> const-bool ast.type == \map | |
245 | ||
246 | 'assoc': fn (m, ...params) -> | |
247 | check-type 'assoc', 0, \map, m.type | |
248 | ||
249 | # Turn the params into a map, this is kind of hacky. | |
250 | params-map = list-to-map params | |
251 | ||
252 | # Copy the map by cloning (prototyping). | |
253 | new-map = ^^m.value | |
254 | ||
255 | for k, v of params-map.value | |
256 | new-map[k] = v | |
257 | ||
258 | {type: \map, value: new-map} | |
259 | ||
260 | 'dissoc': fn (m, ...keys) -> | |
261 | check-type 'dissoc', 0, \map, m.type | |
262 | ||
263 | # Convert keyword to map key strings. | |
264 | str-keys = keys |> map map-keyword | |
265 | ||
266 | new-map = m.value | |
267 | |> obj-to-pairs | |
268 | |> reject ([key, value]) -> key in str-keys | |
269 | |> pairs-to-obj | |
270 | ||
271 | {type: \map, value: new-map} | |
272 | ||
273 | 'get': fn (m, key) -> | |
274 | if m.type == \const and m.value == \nil | |
275 | then return const-nil! | |
276 | ||
277 | check-type 'get', 0, \map, m.type | |
278 | str-key = map-keyword key | |
279 | value = m.value[str-key] | |
280 | if value then value else const-nil! | |
281 | ||
282 | 'contains?': fn (m, key) -> | |
283 | check-type 'contains?', 0, \map, m.type | |
284 | str-key = map-keyword key | |
285 | const-bool (str-key of m.value) | |
286 | ||
287 | 'keys': fn (m) -> | |
288 | check-type 'keys', 0, \map, m.type | |
289 | result = keys m.value |> map (key) -> | |
290 | if key.startsWith keyword-prefix | |
291 | then {type: \keyword, value: key.substring 1} | |
292 | else {type: \string, value: key} | |
293 | {type: \list, value: result} | |
294 | ||
295 | 'vals': fn (m) -> | |
296 | check-type 'vals', 0, \map, m.type | |
297 | {type: \list, value: values m.value} | |
298 | ||
299 | 'sequential?': fn (ast) -> const-bool list-or-vector ast | |
4d8cfe7e JB |
300 | |
301 | 'with-meta': fn (ast, m) -> | |
302 | ast with {meta: m} | |
303 | ||
304 | 'meta': fn (ast) -> | |
305 | if ast.meta | |
306 | then ast.meta | |
307 | else const-nil! | |
308 | ||
309 | 'readline': fn (prompt) -> | |
310 | check-type 'readline', 0, \string, prompt.type | |
311 | result = readline prompt.value | |
312 | if result? | |
313 | then const-str result | |
314 | else const-nil! | |
315 | ||
316 | 'time-ms': fn -> | |
317 | const-int (new Date).getTime! | |
318 | ||
319 | 'conj': fn (list, ...params) -> | |
320 | check-param 'conj', 0, (list-or-vector list), | |
321 | 'list or vector', list.type | |
322 | ||
323 | if list.type == \list | |
324 | type: \list | |
325 | value: (reverse params) ++ list.value | |
326 | else | |
327 | type: \vector | |
328 | value: list.value ++ params | |
329 | ||
330 | 'string?': fn (ast) -> const-bool ast.type == \string | |
331 | ||
332 | 'seq': fn (seq) -> | |
333 | switch seq.type | |
334 | | \list => | |
335 | if seq.value.length | |
336 | then seq | |
337 | else const-nil! | |
338 | | \vector => | |
339 | if seq.value.length | |
340 | then {type: \list, value: seq.value} | |
341 | else const-nil! | |
342 | | \string => | |
343 | if seq.value.length | |
344 | then {type: \list, value: chars seq.value |> map const-str} | |
345 | else const-nil! | |
346 | | otherwise => | |
347 | if seq.type == \const and seq.value == \nil | |
348 | then const-nil! | |
349 | else runtime-error "unsupported type for 'seq': #{seq.type}" |