Change quasiquote algorithm
[jackhill/mal.git] / impls / livescript / core.ls
CommitLineData
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 9fs = require 'fs'
4d8cfe7e 10{readline} = require './node_readline'
3181c695
JB
11
12
13export runtime-error = (msg) -> throw new Error msg
14
86e32f4d
JB
15export unpack-tco = (ast) ->
16 if ast.type == \tco
17 then ast.eval!
18 else ast
19
3181c695 20fn = (body) -> {type: \function, value: body}
2ff2d84b 21const-nil = -> {type: \const, value: \nil}
3181c695
JB
22const-int = (int) -> {type: \int, value: int}
23const-bool = (bool) -> {type: \const, value: if bool then \true else \false}
24const-str = (str) -> {type: \string, value: str}
25
26list-or-vector = ({type}) -> type in [\list \vector]
27
894f5ce8
JB
28are-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 32deep-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
49check-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
55check-type = (name, idx, expected, actual) ->
56 check-param name, idx, expected == actual, expected, actual
25bb14c9
JB
57
58
3181c695
JB
59export 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}"