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 |
'quasiquote' => eval_quasiquote env, params
60 |
'defmacro!' => eval_defmacro env, params
61 |
'macroexpand' => eval_macroexpand env, params
62 | otherwise
=> eval_apply env
, ast.value
64 eval_apply env
, ast.value
66 if result.type
== \tco
72 check_params
= (name
, params
, expected
) ->
73 if params.length
!= expected
74 runtime
-error "
'#{name}' expected #{expected} parameters,
78 eval_def
= (env
, params
) ->
79 check_params
'def!', params, 2
81 #
Name is in the first parameter
, and is not evaluated.
83 if name.type
!= \symbol
84 runtime
-error "expected a symbol for the first parameter
85 of def
!, got a #
{name.type
}"
87 #
Evaluate the second parameter and store
88 # it under name in the env.
89 env.set name.value
, (eval_ast env
, params
[1])
92 eval_let
= (env
, params
) ->
93 check_params
'let*', params, 2
95 binding_list
= params
[0]
96 if binding_list.type not in
[\list \vector
]
97 runtime
-error "expected
1st parameter of
'let*' to
98 be a binding list
(or vector
),
99 got a #
{binding_list.type
}"
100 else if binding_list.value.length %
2 != 0
101 runtime
-error "binding list of
'let*' must have an even
102 number of parameters"
104 # Make a new environment with the
105 # current environment as outer.
106 let_env
= new Env env
108 #
Evaluate all binding values in the
112 |
> each
([binding_name
, binding_value
]) ->
113 if binding_name.type
!= \symbol
114 runtime
-error "expected a symbol as binding name
,
115 got a #
{binding_name.type
}"
117 let_env.set binding_name.value
, (eval_ast let_env
, binding_value
)
119 # Defer evaluation of let
* body with TCO.
120 defer
-tco let_env
, params
[1]
123 eval_do
= (env
, params
) ->
124 if params.length
== 0
125 runtime
-error "
'do' expected at least one parameter"
127 [...rest
, last
-param
] = params
128 rest |
> each eval_ast env
129 defer
-tco env
, last
-param
132 eval_if
= (env
, params
) ->
134 runtime
-error "
'if' expected at least 2 parameters"
135 else if params.length
> 3
136 runtime
-error "
'if' expected at most 3 parameters"
138 cond
= eval_ast env
, params
[0]
140 defer
-tco env
, params
[1]
141 else if params.length
> 2
142 defer
-tco env
, params
[2]
144 {type
: \const
, value
: \nil
}
147 eval_fn
= (env
, params
) ->
148 check_params
'fn*', params, 2
150 if params
[0].type not in
[\list \vector
]
151 runtime
-error "
'fn*' expected first parameter to be a list or vector."
153 if not all
(.type
== \symbol
), params
[0].value
154 runtime
-error "
'fn*' expected only symbols in the parameters list."
156 binds
= params
[0].value |
> map
(.value
)
159 # Parse variadic bind.
161 [...rest
, amper
, name
] = binds
162 if amper
== '&' and name != '&'
166 if elem
-index
'&', binds
167 runtime
-error "
'fn*' invalid usage of variadic parameters."
169 if
(unique binds
).length
!= binds.length
170 runtime
-error "
'fn*' duplicate symbols in parameters list."
174 fn_instance
= (...values
) ->
175 if not vargs and values.length
!= binds.length
176 runtime
-error "function expected #
{binds.length
} parameters
,
177 got #
{values.length
}"
178 else if vargs and values.length
< binds.length
179 runtime
-error "function expected at least
180 #
{binds.length
} parameters
,
181 got #
{values.length
}"
183 #
Set binds to values in the new env.
186 for
[name
, value
] in
(zip binds
, values
)
187 fn_env.set name
, value
191 make
-list values.slice binds.length
193 # Defer evaluation of the function body to TCO.
194 defer
-tco fn_env
, body
196 {type
: \function
, value
: fn_instance
, is_macro
: false
}
199 eval_apply
= (env
, list
) ->
200 [fn
, ...args
] = list |
> map eval_ast env
201 if fn.type
!= \function
202 runtime
-error "#
{fn.value
} is not a function"
204 fn.value.apply env
, args
207 eval_quote
= (env
, params
) ->
208 if params.length
!= 1
209 runtime
-error "quote expected
1 parameter
, got #
{params.length
}"
214 is
-pair
= (ast
) -> ast.type in
[\list \vector
] and ast.value.length
!= 0
217 eval_quasiquote
= (env
, params
) ->
218 if params.length
!= 1
219 runtime
-error "quasiquote expected
1 parameter
, got #
{params.length
}"
222 new
-ast
= if not is
-pair ast
223 make
-call
'quote', [ast]
224 else if is
-symbol ast.value
[0], 'unquote'
226 else if is
-pair ast.value
[0] and \
227 is
-symbol ast.value
[0].value
[0], 'splice-unquote'
228 make
-call
'concat', [
229 ast.value
[0].value
[1]
230 make
-call
'quasiquote', [make-list ast.value[1 to]]
234 make
-call
'quasiquote', [ast.value[0]]
235 make
-call
'quasiquote', [make-list ast.value[1 to]]
238 defer
-tco env
, new
-ast
241 eval_defmacro
= (env
, params
) ->
242 check_params
'def!', params, 2
244 #
Name is in the first parameter
, and is not evaluated.
246 if name.type
!= \symbol
247 runtime
-error "expected a symbol for the first parameter
248 of defmacro
!, got a #
{name.type
}"
250 #
Evaluate the second parameter.
251 fn
= eval_ast env
, params
[1]
252 if fn.type
!= \function
253 runtime
-error "expected a function for the second parameter
254 of defmacro
!, got a #
{fn.type
}"
256 # Copy fn and mark the function as a macro.
257 macro_fn
= fn with is_macro
: true
258 env.set name.value
, macro_fn
261 get
-macro
-fn
= (env
, ast
) ->
262 if ast.type
== \list and
263 ast.value.length
!= 0 and
264 ast.value
[0].type
== \symbol
265 fn
= env.try
-get ast.value
[0].value
266 if fn and fn.type
== \function and fn.is_macro
270 macroexpand
= (env
, ast
) ->
271 loop # until ast is not a macro function call.
272 macro_fn
= get
-macro
-fn env
, ast
273 if not macro_fn then return ast
274 ast
= unpack
-tco
<| macro_fn.value.apply env
, ast.value
[1 to
]
277 eval_macroexpand
= (env
, params
) ->
278 if params.length
!= 1
279 runtime
-error "
'macroexpand' expected 1 parameter,
280 got #
{params.length
}"
282 macroexpand env
, params
[0]
286 for symbol
, value of ns
287 repl_env.set symbol
, value
290 repl_env.set
'eval', do
292 value
: (ast
) -> eval_ast repl_env
, ast # or use current env?
(@
= this
).
294 #
Read, Evaluate, Print
299 |
> (ast
) -> pr_str ast
, print_readably
=true
308 (str "
(do "
(slurp f
) "
)"
)))))'
311 # Parse program arguments.
312 # The first two
(exe and core
-file
) are
, respectively
,
313 # the interpreter executable
(nodejs or lsc
) and the
314 # source file being executed
(stepX_
*.
(ls|js
)).
315 [exe
, core
-file
, mal
-file
, ...argv
] = process.argv
317 repl_env.set
'*ARGV*', do
319 value
: argv |
> map
(arg
) ->
325 rep "
(load
-file \"#
{mal
-file
}\"
)"
329 line
= readline.readline
'user> '
330 break if not line? or line
== ''
334 console.error message