1 readline
= require
'./node_readline'
2 {id
, map
, each
, last
, all
, unique
, zip
, Obj
, elem
-index
} = require
'prelude-ls'
3 {read_str
} = require
'./reader'
4 {pr_str
} = require
'./printer'
5 {Env
} = require
'./env'
6 {runtime
-error
, ns
, unpack
-tco
} = require
'./core'
7 {list
-to
-pairs
} = require
'./utils'
10 defer
-tco
= (env
, ast
) ->
14 eval
: -> eval_ast env
, ast
17 is
-thruthy
= ({type
, value
}) ->
18 type
!= \const or value not in
[\nil \false
]
21 fmap
-ast
= (fn
, {type
, value
}: ast
) -->
22 {type
: type
, value
: fn value
}
25 make
-symbol
= (name
) -> {type
: \symbol
, value
: name
}
26 make
-list
= (value
) -> {type
: \list
, value
: value
}
27 make
-call
= (name
, params
) -> make
-list
[make
-symbol name
] ++ params
28 is
-symbol
= (ast
, name
) -> ast.type
== \symbol and ast.value
== name
31 eval_simple
= (env
, {type
, value
}: ast
) ->
33 | \symbol
=> env.get value
34 | \list
, \vector
=> ast |
> fmap
-ast map eval_ast env
35 | \map
=> ast |
> fmap
-ast Obj.map eval_ast env
39 eval_ast
= (env
, ast
) -->
42 return eval_simple env
, ast
44 ast
= macroexpand env
, ast
46 return eval_simple env
, ast
47 else if ast.value.length
== 0
50 result
= if ast.value
[0].type
== \symbol
51 params
= ast.value
[1 to
]
52 switch ast.value
[0].value
53 |
'def!' => eval_def env, params
54 |
'let*' => eval_let env, params
55 |
'do' => eval_do env, params
56 |
'if' => eval_if env, params
57 |
'fn*' => eval_fn env, params
58 |
'quote' => eval_quote env, params
59 |
'quasiquoteexpand' => eval_quasiquoteexpand params
60 |
'quasiquote' => eval_quasiquote env, params
61 |
'defmacro!' => eval_defmacro env, params
62 |
'macroexpand' => eval_macroexpand env, params
63 |
'try*' => eval_try env, params
64 | otherwise
=> eval_apply env
, ast.value
66 eval_apply env
, ast.value
68 if result.type
== \tco
74 check_params
= (name
, params
, expected
) ->
75 if params.length
!= expected
76 runtime
-error "
'#{name}' expected #{expected} parameters,
80 eval_def
= (env
, params
) ->
81 check_params
'def!', params, 2
83 #
Name is in the first parameter
, and is not evaluated.
85 if name.type
!= \symbol
86 runtime
-error "expected a symbol for the first parameter
87 of def
!, got a #
{name.type
}"
89 #
Evaluate the second parameter and store
90 # it under name in the env.
91 env.set name.value
, (eval_ast env
, params
[1])
94 eval_let
= (env
, params
) ->
95 check_params
'let*', params, 2
97 binding_list
= params
[0]
98 if binding_list.type not in
[\list \vector
]
99 runtime
-error "expected
1st parameter of
'let*' to
100 be a binding list
(or vector
),
101 got a #
{binding_list.type
}"
102 else if binding_list.value.length %
2 != 0
103 runtime
-error "binding list of
'let*' must have an even
104 number of parameters"
106 # Make a new environment with the
107 # current environment as outer.
108 let_env
= new Env env
110 #
Evaluate all binding values in the
114 |
> each
([binding_name
, binding_value
]) ->
115 if binding_name.type
!= \symbol
116 runtime
-error "expected a symbol as binding name
,
117 got a #
{binding_name.type
}"
119 let_env.set binding_name.value
, (eval_ast let_env
, binding_value
)
121 # Defer evaluation of let
* body with TCO.
122 defer
-tco let_env
, params
[1]
125 eval_do
= (env
, params
) ->
126 if params.length
== 0
127 runtime
-error "
'do' expected at least one parameter"
129 [...rest
, last
-param
] = params
130 rest |
> each eval_ast env
131 defer
-tco env
, last
-param
134 eval_if
= (env
, params
) ->
136 runtime
-error "
'if' expected at least 2 parameters"
137 else if params.length
> 3
138 runtime
-error "
'if' expected at most 3 parameters"
140 cond
= eval_ast env
, params
[0]
142 defer
-tco env
, params
[1]
143 else if params.length
> 2
144 defer
-tco env
, params
[2]
146 {type
: \const
, value
: \nil
}
149 eval_fn
= (env
, params
) ->
150 check_params
'fn*', params, 2
152 if params
[0].type not in
[\list \vector
]
153 runtime
-error "
'fn*' expected first parameter to be a list or vector."
155 if not all
(.type
== \symbol
), params
[0].value
156 runtime
-error "
'fn*' expected only symbols in the parameters list."
158 binds
= params
[0].value |
> map
(.value
)
161 # Parse variadic bind.
163 [...rest
, amper
, name
] = binds
164 if amper
== '&' and name != '&'
168 if elem
-index
'&', binds
169 runtime
-error "
'fn*' invalid usage of variadic parameters."
171 if
(unique binds
).length
!= binds.length
172 runtime
-error "
'fn*' duplicate symbols in parameters list."
176 fn_instance
= (...values
) ->
177 if not vargs and values.length
!= binds.length
178 runtime
-error "function expected #
{binds.length
} parameters
,
179 got #
{values.length
}"
180 else if vargs and values.length
< binds.length
181 runtime
-error "function expected at least
182 #
{binds.length
} parameters
,
183 got #
{values.length
}"
185 #
Set binds to values in the new env.
188 for
[name
, value
] in
(zip binds
, values
)
189 fn_env.set name
, value
193 make
-list values.slice binds.length
195 # Defer evaluation of the function body to TCO.
196 defer
-tco fn_env
, body
198 {type
: \function
, value
: fn_instance
, is_macro
: false
}
201 eval_apply
= (env
, list
) ->
202 [fn
, ...args
] = list |
> map eval_ast env
203 if fn.type
!= \function
204 runtime
-error "#
{fn.value
} is not a function
, got a #
{fn.type
}"
206 fn.value.apply env
, args
209 eval_quote
= (env
, params
) ->
210 if params.length
!= 1
211 runtime
-error "quote expected
1 parameter
, got #
{params.length
}"
216 eval_quasiquoteexpand
= (params
) ->
217 if params.length
!= 1
218 runtime
-error "quasiquote expected
1 parameter
, got #
{params.length
}"
224 quasiquote
= (ast
) ->
225 if ast.type in
[\symbol
, \map
]
226 make
-call
'quote', [ast]
227 else if ast.type
== \vector
228 make
-call
'vec', [qq_foldr ast.value]
229 else if ast.type
!= \list
231 else if
(ast.value.length
== 2) and is
-symbol ast.value
[0], 'unquote'
238 result
= make
-list
[]
239 for i from xs.length
- 1 to
0 by
-1
240 result
:= qq_loop xs
[i
], result
244 qq_loop
= (elt
, acc
) ->
245 if elt.type
== \list and \
246 elt.value.length
== 2 and \
247 is
-symbol elt.value
[0], 'splice-unquote'
248 make
-call
'concat', [
259 eval_quasiquote
= (env
, params
) ->
260 new
-ast
= eval_quasiquoteexpand params
261 defer
-tco env
, new
-ast
264 eval_defmacro
= (env
, params
) ->
265 check_params
'def!', params, 2
267 #
Name is in the first parameter
, and is not evaluated.
269 if name.type
!= \symbol
270 runtime
-error "expected a symbol for the first parameter
271 of defmacro
!, got a #
{name.type
}"
273 #
Evaluate the second parameter.
274 fn
= eval_ast env
, params
[1]
275 if fn.type
!= \function
276 runtime
-error "expected a function for the second parameter
277 of defmacro
!, got a #
{fn.type
}"
279 # Copy fn and mark the function as a macro.
280 macro_fn
= fn with is_macro
: true
281 env.set name.value
, macro_fn
284 get
-macro
-fn
= (env
, ast
) ->
285 if ast.type
== \list and
286 ast.value.length
!= 0 and
287 ast.value
[0].type
== \symbol
288 fn
= env.try
-get ast.value
[0].value
289 if fn and fn.type
== \function and fn.is_macro
293 macroexpand
= (env
, ast
) ->
294 loop # until ast is not a macro function call.
295 macro_fn
= get
-macro
-fn env
, ast
296 if not macro_fn then return ast
297 ast
= unpack
-tco
<| macro_fn.value.apply env
, ast.value
[1 to
]
300 eval_macroexpand
= (env
, params
) ->
301 if params.length
!= 1
302 runtime
-error "
'macroexpand' expected 1 parameter,
303 got #
{params.length
}"
305 macroexpand env
, params
[0]
308 eval_try
= (env
, params
) ->
310 runtime
-error "
'try*' expected 1 or 2 parameters,
311 got #
{params.length
}"
313 if params.length
== 1
314 return eval_ast env
, try
-form
316 catch
-clause
= params
[1]
317 if catch
-clause.type
!= \list or
318 catch
-clause.value.length
!= 3 or
319 not
(is
-symbol catch
-clause.value
[0], 'catch*') or
320 catch
-clause.value
[1].type
!= \symbol
321 runtime
-error "
'try*' expected the second parameter to be
322 of the form
(catch
* A B
)"
325 eval_ast env
, try
-form
327 error
-symbol
= catch
-clause.value
[1].value
330 then
{type
: \string
, value
: error.message
}
333 catch
-env
= new Env env
334 catch
-env.set error
-symbol
, error
-value
335 eval_ast catch
-env
, catch
-clause.value
[2]
339 for symbol
, value of ns
340 repl_env.set symbol
, value
343 repl_env.set
'eval', do
345 value
: (ast
) -> eval_ast repl_env
, ast # or use current env?
(@
= this
).
347 #
Read, Evaluate, Print
352 |
> (ast
) -> pr_str ast
, print_readably
=true
356 rep
'(def! not (fn* (x) (if x false true)))'
364 (str "
(do "
(slurp f
) "\nnil
)"
)))))'
371 (list \
'if (first xs)
374 (throw "odd number of forms to cond"
))
375 (cons \
'cond (rest (rest xs)))))))'
377 # Parse program arguments.
378 # The first two
(exe and core
-file
) are
, respectively
,
379 # the interpreter executable
(nodejs or lsc
) and the
380 # source file being executed
(stepX_
*.
(ls|js
)).
381 [exe
, core
-file
, mal
-file
, ...argv
] = process.argv
383 repl_env.set
'*ARGV*', do
385 value
: argv |
> map
(arg
) ->
391 rep "
(load
-file \"#
{mal
-file
}\"
)"
395 line
= readline.readline
'user> '
396 break if not line? or line
== ''
401 then console.error error.message
402 else console.error "
Error:"
, pr_str error
, print_readably
=true