Commit | Line | Data |
---|---|---|
7838e339 | 1 | #!/usr/bin/env bash |
31690700 | 2 | |
31690700 | 3 | source $(dirname $0)/reader.sh |
ea81a808 | 4 | source $(dirname $0)/printer.sh |
ea81a808 | 5 | source $(dirname $0)/env.sh |
8cb5cda4 | 6 | source $(dirname $0)/core.sh |
31690700 | 7 | |
86b689f3 | 8 | # read |
31690700 | 9 | READ () { |
8cb5cda4 | 10 | [ "${1}" ] && r="${1}" || READLINE |
31690700 JM |
11 | READ_STR "${r}" |
12 | } | |
13 | ||
86b689f3 | 14 | # eval |
31690700 | 15 | IS_PAIR () { |
db4c329a | 16 | if _sequential? "${1}"; then |
31690700 JM |
17 | _count "${1}" |
18 | [[ "${r}" > 0 ]] && return 0 | |
19 | fi | |
20 | return 1 | |
21 | } | |
22 | ||
23 | QUASIQUOTE () { | |
24 | if ! IS_PAIR "${1}"; then | |
ea81a808 JM |
25 | _symbol quote |
26 | _list "${r}" "${1}" | |
31690700 JM |
27 | return |
28 | else | |
29 | _nth "${1}" 0; local a0="${r}" | |
30 | if [[ "${ANON["${a0}"]}" == "unquote" ]]; then | |
31 | _nth "${1}" 1 | |
32 | return | |
33 | elif IS_PAIR "${a0}"; then | |
34 | _nth "${a0}" 0; local a00="${r}" | |
35 | if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then | |
ea81a808 | 36 | _symbol concat; local a="${r}" |
31690700 | 37 | _nth "${a0}" 1; local b="${r}" |
8cb5cda4 | 38 | _rest "${1}" |
31690700 | 39 | QUASIQUOTE "${r}"; local c="${r}" |
ea81a808 | 40 | _list "${a}" "${b}" "${c}" |
31690700 JM |
41 | return |
42 | fi | |
43 | fi | |
44 | fi | |
ea81a808 | 45 | _symbol cons; local a="${r}" |
31690700 | 46 | QUASIQUOTE "${a0}"; local b="${r}" |
8cb5cda4 | 47 | _rest "${1}" |
31690700 | 48 | QUASIQUOTE "${r}"; local c="${r}" |
ea81a808 | 49 | _list "${a}" "${b}" "${c}" |
31690700 JM |
50 | return |
51 | } | |
52 | ||
53 | IS_MACRO_CALL () { | |
54 | if ! _list? "${1}"; then return 1; fi | |
55 | _nth "${1}" 0; local a0="${r}" | |
56 | if _symbol? "${a0}"; then | |
b8ee29b2 | 57 | ENV_FIND "${2}" "${a0}" |
31690700 | 58 | if [[ "${r}" ]]; then |
b8ee29b2 JM |
59 | ENV_GET "${2}" "${a0}" |
60 | [ "${ANON["${r}_ismacro_"]}" ] | |
61 | return $? | |
31690700 JM |
62 | fi |
63 | fi | |
64 | return 1 | |
65 | } | |
66 | ||
67 | MACROEXPAND () { | |
68 | local ast="${1}" env="${2}" | |
69 | while IS_MACRO_CALL "${ast}" "${env}"; do | |
70 | _nth "${ast}" 0; local a0="${r}" | |
b8ee29b2 | 71 | ENV_GET "${env}" "${a0}"; local mac="${ANON["${r}"]}" |
8cb5cda4 | 72 | _rest "${ast}" |
31690700 JM |
73 | ${mac%%@*} ${ANON["${r}"]} |
74 | ast="${r}" | |
75 | done | |
76 | r="${ast}" | |
77 | } | |
78 | ||
79 | ||
80 | EVAL_AST () { | |
81 | local ast="${1}" env="${2}" | |
82 | #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" | |
83 | _obj_type "${ast}"; local ot="${r}" | |
84 | case "${ot}" in | |
85 | symbol) | |
b8ee29b2 | 86 | ENV_GET "${env}" "${ast}" |
31690700 JM |
87 | return ;; |
88 | list) | |
ea81a808 | 89 | _map_with_type _list EVAL "${ast}" "${env}" ;; |
31690700 | 90 | vector) |
ea81a808 | 91 | _map_with_type _vector EVAL "${ast}" "${env}" ;; |
31690700 | 92 | hash_map) |
33d33bb3 | 93 | local res="" key= val="" hm="${ANON["${ast}"]}" |
ea81a808 | 94 | _hash_map; local new_hm="${r}" |
31690700 JM |
95 | eval local keys="\${!${hm}[@]}" |
96 | for key in ${keys}; do | |
97 | eval val="\${${hm}[\"${key}\"]}" | |
98 | EVAL "${val}" "${env}" | |
ea81a808 | 99 | _assoc! "${new_hm}" "${key}" "${r}" |
31690700 JM |
100 | done |
101 | r="${new_hm}" ;; | |
102 | *) | |
103 | r="${ast}" ;; | |
104 | esac | |
105 | } | |
106 | ||
31690700 JM |
107 | EVAL () { |
108 | local ast="${1}" env="${2}" | |
109 | while true; do | |
110 | r= | |
111 | [[ "${__ERROR}" ]] && return 1 | |
112 | #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" | |
113 | if ! _list? "${ast}"; then | |
114 | EVAL_AST "${ast}" "${env}" | |
115 | return | |
116 | fi | |
117 | ||
118 | # apply list | |
119 | MACROEXPAND "${ast}" "${env}" | |
120 | ast="${r}" | |
0d629719 DM |
121 | if ! _list? "${ast}"; then |
122 | EVAL_AST "${ast}" "${env}" | |
123 | return | |
124 | fi | |
3636e7dd DM |
125 | _empty? "${ast}" && r="${ast}" && return |
126 | ||
31690700 JM |
127 | _nth "${ast}" 0; local a0="${r}" |
128 | _nth "${ast}" 1; local a1="${r}" | |
129 | _nth "${ast}" 2; local a2="${r}" | |
130 | case "${ANON["${a0}"]}" in | |
b8ee29b2 JM |
131 | def!) EVAL "${a2}" "${env}" |
132 | [[ "${__ERROR}" ]] && return 1 | |
133 | ENV_SET "${env}" "${a1}" "${r}" | |
31690700 | 134 | return ;; |
5bbc7a1f | 135 | let__STAR__) ENV "${env}"; local let_env="${r}" |
31690700 JM |
136 | local let_pairs=(${ANON["${a1}"]}) |
137 | local idx=0 | |
138 | #echo "let: [${let_pairs[*]}] for ${a2}" | |
139 | while [[ "${let_pairs["${idx}"]}" ]]; do | |
140 | EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" | |
b8ee29b2 | 141 | ENV_SET "${let_env}" "${let_pairs[${idx}]}" "${r}" |
31690700 JM |
142 | idx=$(( idx + 2)) |
143 | done | |
6301e0b6 JM |
144 | ast="${a2}" |
145 | env="${let_env}" | |
146 | # Continue loop | |
147 | ;; | |
31690700 JM |
148 | quote) |
149 | r="${a1}" | |
150 | return ;; | |
151 | quasiquote) | |
152 | QUASIQUOTE "${a1}" | |
6301e0b6 JM |
153 | ast="${r}" |
154 | # Continue loop | |
155 | ;; | |
31690700 | 156 | defmacro!) |
31690700 | 157 | EVAL "${a2}" "${env}" |
b8ee29b2 JM |
158 | [[ "${__ERROR}" ]] && return 1 |
159 | ANON["${r}_ismacro_"]="yes" | |
160 | ENV_SET "${env}" "${a1}" "${r}" | |
31690700 JM |
161 | return ;; |
162 | macroexpand) | |
163 | MACROEXPAND "${a1}" "${env}" | |
164 | return ;; | |
5bbc7a1f | 165 | sh__STAR__) EVAL "${a1}" "${env}" |
31690700 JM |
166 | local output="" |
167 | local line="" | |
168 | while read line; do | |
169 | output="${output}${line}\n" | |
170 | done < <(eval ${ANON["${r}"]}) | |
cc021efe | 171 | _string "${output%\\n}" |
31690700 | 172 | return ;; |
5bbc7a1f | 173 | try__STAR__) EVAL "${a1}" "${env}" |
31690700 JM |
174 | [[ -z "${__ERROR}" ]] && return |
175 | _nth "${a2}" 0; local a20="${r}" | |
176 | if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then | |
177 | _nth "${a2}" 1; local a21="${r}" | |
178 | _nth "${a2}" 2; local a22="${r}" | |
ea81a808 | 179 | _list "${a21}"; local binds="${r}" |
31690700 JM |
180 | ENV "${env}" "${binds}" "${__ERROR}" |
181 | local try_env="${r}" | |
182 | __ERROR= | |
b8ee29b2 | 183 | EVAL "${a22}" "${try_env}" |
31690700 JM |
184 | fi # if no catch* clause, just propagate __ERROR |
185 | return ;; | |
186 | do) _count "${ast}" | |
187 | _slice "${ast}" 1 $(( ${r} - 2 )) | |
188 | EVAL_AST "${r}" "${env}" | |
189 | [[ "${__ERROR}" ]] && r= && return 1 | |
8cb5cda4 | 190 | _last "${ast}" |
31690700 JM |
191 | ast="${r}" |
192 | # Continue loop | |
193 | ;; | |
194 | if) EVAL "${a1}" "${env}" | |
b8ee29b2 | 195 | [[ "${__ERROR}" ]] && return 1 |
31690700 JM |
196 | if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then |
197 | # eval false form | |
198 | _nth "${ast}" 3; local a3="${r}" | |
199 | if [[ "${a3}" ]]; then | |
200 | ast="${a3}" | |
201 | else | |
202 | r="${__nil}" | |
203 | return | |
204 | fi | |
205 | else | |
206 | # eval true condition | |
207 | ast="${a2}" | |
208 | fi | |
209 | # Continue loop | |
210 | ;; | |
5bbc7a1f | 211 | fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ |
ea81a808 JM |
212 | EVAL \"${a2}\" \"\${r}\"" \ |
213 | "${a2}" "${env}" "${a1}" | |
31690700 JM |
214 | return ;; |
215 | *) EVAL_AST "${ast}" "${env}" | |
216 | [[ "${__ERROR}" ]] && r= && return 1 | |
217 | local el="${r}" | |
8cb5cda4 JM |
218 | _first "${el}"; local f="${ANON["${r}"]}" |
219 | _rest "${el}"; local args="${ANON["${r}"]}" | |
31690700 JM |
220 | #echo "invoke: [${f}] ${args}" |
221 | if [[ "${f//@/ }" != "${f}" ]]; then | |
222 | set -- ${f//@/ } | |
223 | ast="${2}" | |
224 | ENV "${3}" "${4}" ${args} | |
225 | env="${r}" | |
226 | else | |
227 | eval ${f%%@*} ${args} | |
228 | return | |
229 | fi | |
230 | # Continue loop | |
231 | ;; | |
232 | esac | |
233 | done | |
234 | } | |
ea81a808 | 235 | |
86b689f3 | 236 | |
31690700 JM |
237 | PRINT () { |
238 | if [[ "${__ERROR}" ]]; then | |
239 | _pr_str "${__ERROR}" yes | |
240 | r="Error: ${r}" | |
241 | __ERROR= | |
242 | else | |
243 | _pr_str "${1}" yes | |
244 | fi | |
245 | } | |
246 | ||
86b689f3 | 247 | # repl |
31690700 JM |
248 | ENV; REPL_ENV="${r}" |
249 | REP () { | |
250 | r= | |
70aff0c1 | 251 | READ "${1}" |
31690700 JM |
252 | EVAL "${r}" "${REPL_ENV}" |
253 | PRINT "${r}" | |
254 | } | |
255 | ||
8cb5cda4 | 256 | # core.sh: defined using bash |
b8ee29b2 JM |
257 | _fref () { |
258 | _symbol "${1}"; local sym="${r}" | |
259 | _function "${2} \"\${@}\"" | |
260 | ENV_SET "${REPL_ENV}" "${sym}" "${r}" | |
261 | } | |
ea81a808 | 262 | for n in "${!core_ns[@]}"; do _fref "${n}" "${core_ns["${n}"]}"; done |
ea81a808 | 263 | _eval () { EVAL "${1}" "${REPL_ENV}"; } |
31690700 | 264 | _fref "eval" _eval |
86b689f3 JM |
265 | _list; argv="${r}" |
266 | for _arg in "${@:2}"; do _string "${_arg}"; _conj! "${argv}" "${r}"; done | |
b8ee29b2 JM |
267 | _symbol "__STAR__ARGV__STAR__" |
268 | ENV_SET "${REPL_ENV}" "${r}" "${argv}"; | |
31690700 | 269 | |
8cb5cda4 | 270 | # core.mal: defined using the language itself |
db4c329a | 271 | REP "(def! *host-language* \"bash\")" |
31690700 | 272 | REP "(def! not (fn* (a) (if a false true)))" |
8cb5cda4 | 273 | REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" |
31690700 | 274 | REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" |
14ab099c NB |
275 | REP "(def! inc (fn* [x] (+ x 1)))" |
276 | REP "(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))" | |
166da203 | 277 | REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) \`(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))" |
31690700 | 278 | |
86b689f3 | 279 | # load/run file from command line (then exit) |
31690700 | 280 | if [[ "${1}" ]]; then |
86b689f3 JM |
281 | REP "(load-file \"${1}\")" |
282 | exit 0 | |
283 | fi | |
284 | ||
285 | # repl loop | |
286 | REP "(println (str \"Mal [\" *host-language* \"]\"))" | |
287 | while true; do | |
288 | READLINE "user> " || exit "$?" | |
289 | [[ "${r}" ]] && REP "${r}" && echo "${r}" | |
290 | done |