Fixed wrong ignore of livescript/node_readline.js
[jackhill/mal.git] / livescript / step4_if_fn_do.ls
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'
8
9
10 is-thruthy = ({type, value}) ->
11 type != \const or value not in [\nil \false]
12
13
14 fmap-ast = (fn, {type, value}: ast) -->
15 {type: type, value: fn value}
16
17
18 eval_simple = (env, {type, value}: ast) ->
19 switch type
20 | \symbol => env.get value
21 | \list, \vector => ast |> fmap-ast map eval_ast env
22 | \map => ast |> fmap-ast Obj.map eval_ast env
23 | otherwise => ast
24
25
26 eval_ast = (env, {type, value}: ast) -->
27 if type != \list then eval_simple env, ast
28 else if value.length == 0 then ast
29 else if value[0].type == \symbol
30 params = value[1 to]
31 switch value[0].value
32 | 'def!' => eval_def env, params
33 | 'let*' => eval_let env, params
34 | 'do' => eval_do env, params
35 | 'if' => eval_if env, params
36 | 'fn*' => eval_fn env, params
37 | otherwise => eval_apply env, value
38 else
39 eval_apply env, value
40
41
42 check_params = (name, params, expected) ->
43 if params.length != expected
44 runtime-error "'#{name}' expected #{expected} parameters,
45 got #{params.length}"
46
47
48 eval_def = (env, params) ->
49 check_params 'def!', params, 2
50
51 # Name is in the first parameter, and is not evaluated.
52 name = params[0]
53 if name.type != \symbol
54 runtime-error "expected a symbol for the first parameter
55 of def!, got a #{name.type}"
56
57 # Evaluate the second parameter and store
58 # it under name in the env.
59 env.set name.value, (eval_ast env, params[1])
60
61
62 eval_let = (env, params) ->
63 check_params 'let*', params, 2
64
65 binding_list = params[0]
66 if binding_list.type not in [\list \vector]
67 runtime-error "expected 1st parameter of 'let*' to
68 be a binding list (or vector),
69 got a #{binding_list.type}"
70 else if binding_list.value.length % 2 != 0
71 runtime-error "binding list of 'let*' must have an even
72 number of parameters"
73
74 # Make a new environment with the
75 # current environment as outer.
76 let_env = new Env env
77
78 # Evaluate all binding values in the
79 # new environment.
80 binding_list.value
81 |> list-to-pairs
82 |> each ([binding_name, binding_value]) ->
83 if binding_name.type != \symbol
84 runtime-error "expected a symbol as binding name,
85 got a #{binding_name.type}"
86
87 let_env.set binding_name.value, (eval_ast let_env, binding_value)
88
89 # Evaluate the 'body' of let* with the new environment.
90 eval_ast let_env, params[1]
91
92
93 eval_do = (env, params) ->
94 if params.length == 0
95 runtime-error "'do' expected at least one parameter"
96
97 params |> map eval_ast env |> last
98
99
100 eval_if = (env, params) ->
101 if params.length < 2
102 runtime-error "'if' expected at least 2 parameters"
103 else if params.length > 3
104 runtime-error "'if' expected at most 3 parameters"
105
106 cond = eval_ast env, params[0]
107 if is-thruthy cond
108 eval_ast env, params[1]
109 else if params.length > 2
110 eval_ast env, params[2]
111 else
112 {type: \const, value: \nil}
113
114
115 eval_fn = (env, params) ->
116 check_params 'fn*', params, 2
117
118 if params[0].type not in [\list \vector]
119 runtime-error "'fn*' expected first parameter to be a list or vector."
120
121 if not all (.type == \symbol), params[0].value
122 runtime-error "'fn*' expected only symbols in the parameters list."
123
124 binds = params[0].value |> map (.value)
125 vargs = null
126
127 # Parse variadic bind.
128 if binds.length >= 2
129 [...rest, amper, name] = binds
130 if amper == '&' and name != '&'
131 binds = rest
132 vargs = name
133
134 if elem-index '&', binds
135 runtime-error "'fn*' invalid usage of variadic parameters."
136
137 if (unique binds).length != binds.length
138 runtime-error "'fn*' duplicate symbols in parameters list."
139
140 body = params[1]
141
142 fn_instance = (...values) ->
143 if not vargs and values.length != binds.length
144 runtime-error "function expected #{binds.length} parameters,
145 got #{values.length}"
146 else if vargs and values.length < binds.length
147 runtime-error "function expected at least
148 #{binds.length} parameters,
149 got #{values.length}"
150
151 # Set binds to values in the new env.
152 fn_env = new Env env
153
154 for [name, value] in (zip binds, values)
155 fn_env.set name, value
156
157 if vargs
158 fn_env.set vargs, do
159 type: \list
160 value: values.slice binds.length
161
162 # Evaluate the function body with the new environment.
163 eval_ast fn_env, body
164
165 {type: \function, value: fn_instance}
166
167
168 eval_apply = (env, list) ->
169 [fn, ...args] = list |> map eval_ast env
170 if fn.type != \function
171 runtime-error "#{fn.value} is not a function"
172
173 fn.value.apply env, args
174
175
176 repl_env = new Env
177 for symbol, value of ns
178 repl_env.set symbol, value
179
180
181 rep = (line) ->
182 line
183 |> read_str
184 |> eval_ast repl_env
185 |> (ast) -> pr_str ast, print_readably=true
186
187
188 loop
189 line = readline.readline 'user> '
190 break if not line? or line == ''
191 try
192 console.log rep line
193 catch {message}
194 console.error message