Merge pull request #372 from bjh21/bjh21-empty-hashmap
[jackhill/mal.git] / bash / core.sh
1 #
2 # mal (Make a Lisp) object types
3 #
4
5 if [ -z "${__mal_core_included__}" ]; then
6 __mal_core_included=true
7
8 source $(dirname $0)/types.sh
9 source $(dirname $0)/reader.sh
10 source $(dirname $0)/printer.sh
11
12 # Exceptions/Errors
13
14 throw() {
15 __ERROR="${1}"
16 r=
17 }
18
19
20 # General functions
21
22 obj_type () {
23 _obj_type "${1}"
24 _string "${r}"
25 }
26
27 equal? () {
28 _equal? "${1}" "${2}" && r="${__true}" || r="${__false}"
29 }
30
31
32 # Scalar functions
33
34 nil? () { _nil? "${1}" && r="${__true}" || r="${__false}"; }
35 true? () { _true? "${1}" && r="${__true}" || r="${__false}"; }
36 false? () { _false? "${1}" && r="${__true}" || r="${__false}"; }
37
38
39 # Symbol functions
40
41 symbol () { _symbol "${ANON["${1}"]}"; }
42
43 symbol? () { _symbol? "${1}" && r="${__true}" || r="${__false}"; }
44
45
46 # Keyword functions
47
48 keyword () { _keyword "${ANON["${1}"]}"; }
49
50 keyword? () { _keyword? "${1}" && r="${__true}" || r="${__false}"; }
51
52
53 # Number functions
54
55 number? () { _number? "${1}" && r="${__true}" || r="${__false}"; }
56
57 num_plus () { r=$(( ${ANON["${1}"]} + ${ANON["${2}"]} )); _number "${r}"; }
58 num_minus () { r=$(( ${ANON["${1}"]} - ${ANON["${2}"]} )); _number "${r}"; }
59 num_multiply () { r=$(( ${ANON["${1}"]} * ${ANON["${2}"]} )); _number "${r}"; }
60 num_divide () { r=$(( ${ANON["${1}"]} / ${ANON["${2}"]} )); _number "${r}"; }
61
62 _num_bool () { [[ "${1}" = "1" ]] && r="${__true}" || r="${__false}"; }
63 num_gt () { r=$(( ${ANON["${1}"]} > ${ANON["${2}"]} )); _num_bool "${r}"; }
64 num_gte () { r=$(( ${ANON["${1}"]} >= ${ANON["${2}"]} )); _num_bool "${r}"; }
65 num_lt () { r=$(( ${ANON["${1}"]} < ${ANON["${2}"]} )); _num_bool "${r}"; }
66 num_lte () { r=$(( ${ANON["${1}"]} <= ${ANON["${2}"]} )); _num_bool "${r}"; }
67
68 # return number of milliseconds since epoch
69 time_ms () {
70 local ms=$(date +%s%3N)
71 _number "${ms}"
72 }
73
74
75 # String functions
76
77 string? () { _string? "${1}" && ( ! _keyword? "${1}" ) && r="${__true}" || r="${__false}"; }
78
79 pr_str () {
80 local res=""
81 for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done
82 _string "${res:1}"
83 }
84
85 str () {
86 local res=""
87 for x in "${@}"; do _pr_str "${x}"; res="${res}${r}"; done
88 _string "${res}"
89 }
90
91 prn () {
92 local res=""
93 for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done
94 echo "${res:1}"
95 r="${__nil}";
96 }
97
98 println () {
99 local res=""
100 for x in "${@}"; do _pr_str "${x}"; res="${res} ${r}"; done
101 echo "${res:1}"
102 r="${__nil}";
103 }
104
105 readline () {
106 READLINE "${ANON["${1}"]}" && _string "${r}" || r="${__nil}"
107 }
108
109 read_string () {
110 READ_STR "${ANON["${1}"]}"
111 }
112
113 slurp () {
114 local lines
115 mapfile lines < "${ANON["${1}"]}"
116 local text="${lines[*]}"; text=${text//$'\n' /$'\n'}
117 _string "${text}"
118 }
119
120
121 # Function functions
122 function? () { _function? "${1}" && [ -z "${ANON["${1}_ismacro_"]}" ] && r="${__true}" || r="${__false}"; }
123 macro? () { _function? "${1}" && [ "${ANON["${1}_ismacro_"]}" ] && r="${__true}" || r="${__false}"; }
124
125
126 # List functions
127 list? () { _list? "${1}" && r="${__true}" || r="${__false}"; }
128
129
130 # Vector functions (same as lists for now)
131 vector? () { _vector? "${1}" && r="${__true}" || r="${__false}"; }
132
133
134 # Hash map (associative array) functions
135 hash_map? () { _hash_map? "${1}" && r="${__true}" || r="${__false}"; }
136
137 # Return new hash map with keys/values updated
138 assoc () {
139 if ! _hash_map? "${1}"; then
140 _error "assoc onto non-hash-map"
141 return
142 fi
143 _copy_hash_map "${1}"; shift
144 local name="${r}"
145 local obj=${ANON["${name}"]}
146 declare -A -g ${obj}
147
148 while [[ "${1}" ]]; do
149 eval ${obj}[\"${ANON["${1}"]}\"]=\"${2}\"
150 shift; shift
151 done
152 r="${name}"
153 }
154
155 dissoc () {
156 if ! _hash_map? "${1}"; then
157 _error "dissoc from non-hash-map"
158 return
159 fi
160 _copy_hash_map "${1}"; shift
161 local name="${r}"
162 local obj=${ANON["${name}"]}
163 declare -A -g ${obj}
164
165 while [[ "${1}" ]]; do
166 eval unset ${obj}[\"${ANON["${1}"]}\"]
167 shift
168 done
169 r="${name}"
170 }
171
172 _get () {
173 _obj_type "${1}"; local ot="${r}"
174 case "${ot}" in
175 hash_map)
176 local obj="${ANON["${1}"]}"
177 eval r="\${${obj}[\"${2}\"]}" ;;
178 list|vector)
179 _nth "${1}" "${2}" ;;
180 nil)
181 r="${__nil}" ;;
182 esac
183 }
184 get () {
185 _get "${1}" "${ANON["${2}"]}"
186 [[ "${r}" ]] || r="${__nil}"
187 }
188
189 contains? () { _contains? "${1}" "${ANON["${2}"]}" && r="${__true}" || r="${__false}"; }
190
191 keys () {
192 local obj="${ANON["${1}"]}"
193 local kstrs=
194 eval local keys="\${!${obj}[@]}"
195 for k in ${keys}; do
196 _string "${k}"
197 kstrs="${kstrs} ${r}"
198 done
199
200 __new_obj_hash_code
201 r="list_${r}"
202 ANON["${r}"]="${kstrs:1}"
203 }
204
205 vals () {
206 local obj="${ANON["${1}"]}"
207 local kvals=
208 local val=
209 eval local keys="\${!${obj}[@]}"
210 for k in ${keys}; do
211 eval val="\${${obj}["\${k}"]}"
212 kvals="${kvals} ${val}"
213 done
214
215 __new_obj_hash_code
216 r="list_${r}"
217 ANON["${r}"]="${kvals:1}"
218 }
219
220
221 # sequence operations
222
223 sequential? () {
224 _sequential? "${1}" && r="${__true}" || r="${__false}"
225 }
226
227 cons () {
228 _list ${1} ${ANON["${2}"]}
229 }
230
231 concat () {
232 _list
233 local acc=""
234 for item in "${@}"; do
235 acc="${acc} ${ANON["${item}"]}"
236 done
237 ANON["${r}"]="${acc:1}"
238 }
239
240 nth () {
241 _nth "${1}" "${ANON["${2}"]}"
242 if [ -z "${r}" ]; then
243 _error "nth: index out of bounds"
244 return
245 fi
246 }
247
248 empty? () { _empty? "${1}" && r="${__true}" || r="${__false}"; }
249
250 count () {
251 _count "${1}"
252 _number "${r}"
253 }
254
255 apply () {
256 local f="${ANON["${1}"]}"; shift
257 local items="${@:1:$(( ${#@} -1 ))} ${ANON["${!#}"]}"
258 eval ${f%%@*} ${items}
259 }
260
261 # Takes a function object and an list object and invokes the function
262 # on each element of the list, returning a new list of the results.
263 map () {
264 local f="${ANON["${1}"]}"; shift
265 #echo _map "${f}" "${@}"
266 _map "${f}" "${@}"
267 }
268
269 conj () {
270 local obj="${1}"; shift
271 local obj_data="${ANON["${obj}"]}"
272 __new_obj_like "${obj}"
273 if _list? "${obj}"; then
274 ANON["${r}"]="${obj_data:+${obj_data}}"
275 for elem in ${@}; do
276 ANON["${r}"]="${elem} ${ANON["${r}"]}"
277 done
278
279 else
280 ANON["${r}"]="${obj_data:+${obj_data} }${*}"
281 fi
282 }
283
284 seq () {
285 local obj="${1}"; shift
286 local obj_data="${ANON["${obj}"]}"
287
288
289 if _list? "${obj}"; then
290 _count "${obj}"
291 if [ "${r}" -eq 0 ]; then r="${__nil}"; return; fi
292 r="${obj}"
293 elif _vector? "${obj}"; then
294 _count "${obj}"
295 if [ "${r}" -eq 0 ]; then r="${__nil}"; return; fi
296 __new_obj_hash_code
297 r="list_${r}"
298 ANON["${r}"]="${obj_data}"
299 elif _string? "${obj}"; then
300 if [ "${#obj_data}" -eq 0 ]; then r="${__nil}"; return; fi
301 local i=0 acc=""
302 for (( i=0; i < ${#obj_data}; i++ )); do
303 _string "${obj_data:$i:1}"
304 acc="${acc} ${r}"
305 done
306 _list
307 ANON["${r}"]="${acc:1}"
308 elif _nil? "${obj}"; then
309 r="${__nil}"
310 else
311 throw "seq: called on non-sequence"
312 fi
313 }
314
315
316 # Metadata functions
317
318 with_meta () {
319 local obj="${1}"; shift
320 local meta_data="${1}"; shift
321 __new_obj_like "${obj}"
322 ANON["${r}"]="${ANON["${obj}"]}"
323 local meta_obj="meta_${r#*_}"
324 ANON["${meta_obj}"]="${meta_data}"
325 }
326
327 meta () {
328 r="${ANON["meta_${1#*_}"]}"
329 [[ "${r}" ]] || r="${__nil}"
330 }
331
332
333 # Atom functions
334
335 atom? () { _atom? "${1}" && r="${__true}" || r="${__false}"; }
336 deref () {
337 # TODO: double-check atom type
338 r=${ANON["${1}"]}
339 }
340 reset_BANG () {
341 local atm="${1}"; shift
342 ANON["${atm}"]="${*}"
343 r="${*}"
344 }
345 swap_BANG () {
346 local atm="${1}"; shift
347 local f="${ANON["${1}"]}"; shift
348 ${f%%@*} "${ANON["${atm}"]}" "${@}"
349 ANON["${atm}"]="${r}"
350 }
351
352
353
354 # Namespace of core functions
355
356 declare -A core_ns=(
357 [type]=obj_type
358 [=]=equal?
359 [throw]=throw
360 [nil?]=nil?
361 [true?]=true?
362 [false?]=false?
363 [string?]=string?
364 [symbol]=symbol
365 [symbol?]=symbol?
366 [keyword]=keyword
367 [keyword?]=keyword?
368 [number?]=number?
369 [fn?]=function?
370 [macro?]=macro?
371
372 [pr-str]=pr_str
373 [str]=str
374 [prn]=prn
375 [println]=println
376 [readline]=readline
377 [read-string]=read_string
378 [slurp]=slurp
379 [<]=num_lt
380 [<=]=num_lte
381 [>]=num_gt
382 [>=]=num_gte
383 [+]=num_plus
384 [-]=num_minus
385 [__STAR__]=num_multiply
386 [/]=num_divide
387 [time-ms]=time_ms
388
389 [list]=_list
390 [list?]=list?
391 [vector]=_vector
392 [vector?]=vector?
393 [hash-map]=_hash_map
394 [map?]=hash_map?
395 [assoc]=assoc
396 [dissoc]=dissoc
397 [get]=get
398 [contains?]=contains?
399 [keys]=keys
400 [vals]=vals
401
402 [sequential?]=sequential?
403 [cons]=cons
404 [concat]=concat
405 [nth]=nth
406 [first]=_first
407 [rest]=_rest
408 [empty?]=empty?
409 [count]=count
410 [apply]=apply
411 [map]=map
412
413 [conj]=conj
414 [seq]=seq
415
416 [with-meta]=with_meta
417 [meta]=meta
418 [atom]=_atom
419 [atom?]=atom?
420 [deref]=deref
421 [reset!]=reset_BANG
422 [swap!]=swap_BANG)
423
424 fi