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
} = 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
, {type
, value
}: ast
) -->
42 return eval_simple env
, ast
43 else if value.length
== 0
46 result
= if value
[0].type
== \symbol
49 |
'def!' => eval_def env, params
50 |
'let*' => eval_let env, params
51 |
'do' => eval_do env, params
52 |
'if' => eval_if env, params
53 |
'fn*' => eval_fn env, params
54 |
'quote' => eval_quote env, params
55 |
'quasiquote' => eval_quasiquote env, params
56 | otherwise
=> eval_apply env
, value
60 if result.type
== \tco
62 {type
, value
}: ast
= result.ast
67 check_params
= (name
, params
, expected
) ->
68 if params.length
!= expected
69 runtime
-error "
'#{name}' expected #{expected} parameters,
73 eval_def
= (env
, params
) ->
74 check_params
'def!', params, 2
76 #
Name is in the first parameter
, and is not evaluated.
78 if name.type
!= \symbol
79 runtime
-error "expected a symbol for the first parameter
80 of def
!, got a #
{name.type
}"
82 #
Evaluate the second parameter and store
83 # it under name in the env.
84 env.set name.value
, (eval_ast env
, params
[1])
87 eval_let
= (env
, params
) ->
88 check_params
'let*', params, 2
90 binding_list
= params
[0]
91 if binding_list.type not in
[\list \vector
]
92 runtime
-error "expected
1st parameter of
'let*' to
93 be a binding list
(or vector
),
94 got a #
{binding_list.type
}"
95 else if binding_list.value.length %
2 != 0
96 runtime
-error "binding list of
'let*' must have an even
99 # Make a new environment with the
100 # current environment as outer.
101 let_env
= new Env env
103 #
Evaluate all binding values in the
107 |
> each
([binding_name
, binding_value
]) ->
108 if binding_name.type
!= \symbol
109 runtime
-error "expected a symbol as binding name
,
110 got a #
{binding_name.type
}"
112 let_env.set binding_name.value
, (eval_ast let_env
, binding_value
)
114 # Defer evaluation of let
* body with TCO.
115 defer
-tco let_env
, params
[1]
118 eval_do
= (env
, params
) ->
119 if params.length
== 0
120 runtime
-error "
'do' expected at least one parameter"
122 [...rest
, last
-param
] = params
123 rest |
> each eval_ast env
124 defer
-tco env
, last
-param
127 eval_if
= (env
, params
) ->
129 runtime
-error "
'if' expected at least 2 parameters"
130 else if params.length
> 3
131 runtime
-error "
'if' expected at most 3 parameters"
133 cond
= eval_ast env
, params
[0]
135 defer
-tco env
, params
[1]
136 else if params.length
> 2
137 defer
-tco env
, params
[2]
139 {type
: \const
, value
: \nil
}
142 eval_fn
= (env
, params
) ->
143 check_params
'fn*', params, 2
145 if params
[0].type not in
[\list \vector
]
146 runtime
-error "
'fn*' expected first parameter to be a list or vector."
148 if not all
(.type
== \symbol
), params
[0].value
149 runtime
-error "
'fn*' expected only symbols in the parameters list."
151 binds
= params
[0].value |
> map
(.value
)
154 # Parse variadic bind.
156 [...rest
, amper
, name
] = binds
157 if amper
== '&' and name != '&'
161 if elem
-index
'&', binds
162 runtime
-error "
'fn*' invalid usage of variadic parameters."
164 if
(unique binds
).length
!= binds.length
165 runtime
-error "
'fn*' duplicate symbols in parameters list."
169 fn_instance
= (...values
) ->
170 if not vargs and values.length
!= binds.length
171 runtime
-error "function expected #
{binds.length
} parameters
,
172 got #
{values.length
}"
173 else if vargs and values.length
< binds.length
174 runtime
-error "function expected at least
175 #
{binds.length
} parameters
,
176 got #
{values.length
}"
178 #
Set binds to values in the new env.
181 for
[name
, value
] in
(zip binds
, values
)
182 fn_env.set name
, value
186 make
-list values.slice binds.length
188 # Defer evaluation of the function body to TCO.
189 defer
-tco fn_env
, body
191 {type
: \function
, value
: fn_instance
}
194 eval_apply
= (env
, list
) ->
195 [fn
, ...args
] = list |
> map eval_ast env
196 if fn.type
!= \function
197 runtime
-error "#
{fn.value
} is not a function"
199 fn.value.apply env
, args
202 eval_quote
= (env
, params
) ->
203 if params.length
!= 1
204 runtime
-error "quote expected
1 parameter
, got #
{params.length
}"
209 is
-pair
= (ast
) -> ast.type in
[\list \vector
] and ast.value.length
!= 0
212 eval_quasiquote
= (env
, params
) ->
213 if params.length
!= 1
214 runtime
-error "quasiquote expected
1 parameter
, got #
{params.length
}"
217 new
-ast
= if not is
-pair ast
218 make
-call
'quote', [ast]
219 else if is
-symbol ast.value
[0], 'unquote'
221 else if is
-pair ast.value
[0] and \
222 is
-symbol ast.value
[0].value
[0], 'splice-unquote'
223 make
-call
'concat', [
224 ast.value
[0].value
[1]
225 make
-call
'quasiquote', [make-list ast.value[1 to]]
229 make
-call
'quasiquote', [ast.value[0]]
230 make
-call
'quasiquote', [make-list ast.value[1 to]]
233 defer
-tco env
, new
-ast
237 for symbol
, value of ns
238 repl_env.set symbol
, value
241 repl_env.set
'eval', do
243 value
: (ast
) -> eval_ast repl_env
, ast # or use current env?
(@
= this
).
250 |
> (ast
) -> pr_str ast
, print_readably
=true
258 (str "
(do "
(slurp f
) "
)"
)))))'
260 # Parse program arguments.
261 # The first two
(exe and core
-file
) are
, respectively
,
262 # the interpreter executable
(nodejs or lsc
) and the
263 # source file being executed
(stepX_
*.
(ls|js
)).
264 [exe
, core
-file
, mal
-file
, ...argv
] = process.argv
266 repl_env.set
'*ARGV*', do
268 value
: argv |
> map
(arg
) ->
273 rep "
(load
-file \"#
{mal
-file
}\"
)"
277 line
= readline.readline
'user> '
278 break if not line? or line
== ''
282 console.error message