13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
|
# File 'lib/taoism/evaluator.rb', line 13
def evaluate(node, env = @global)
case node
when Nodes::Program
node.stmts.map { |s| evaluate(s, env) }.last
when Nodes::Block
inner = Environment.new(env)
last = nil
node.stmts.each { |s| last = evaluate(s, inner) }
last
when Nodes::ExprStmt then evaluate(node.expr, env)
when Nodes::IntLit then node.value
when Nodes::FloatLit then node.value
when Nodes::StringLit
node.value.gsub(/\\(\\|"|n|r|t|0|\((\w+)\))/) do
case $1
when '\\' then '\\'
when '"' then '"'
when 'n' then "\n"
when 'r' then "\r"
when 't' then "\t"
when '0' then "\0"
else
env.get($2).then { |v|
v.is_a?(String) ? v : Runtime.repr(v)
}
end
end
when Nodes::BoolLit then node.value
when Nodes::NoneLit then nil
when Nodes::ListLit
node.elements.map { |e| evaluate(e, env) }
when Nodes::Identifier then env.get(node.name)
when Nodes::Let
val = evaluate(node.value, env)
env.define(node.name, val, mutable: node.mutable)
val
when Nodes::LetPair
values = evaluate(node.expr, env)
unless values.is_a?(Array)
raise Runtime::Error, "cannot destructure #{values}"
end
if values.length < 2
raise Runtime::Error, "cannot destructure #{values}, expected at least 2 elements"
end
env.define(node.first, values[0], mutable: false)
env.define(node.second, values[1], mutable: false)
values
when Nodes::Assign
val = evaluate(node.value, env)
case node.target
when Nodes::Identifier
env.set(node.target.name, val)
when Nodes::DotExpr
target = evaluate(node.target.target, env)
unless target.is_a?(Runtime::Instance)
raise Runtime::Error, "field access on non-instance"
end
unless target.fields.key?(node.target.name)
raise Runtime::Error, "undefined field: #{node.target.name}"
end
target.fields[node.target.name] = val
when Nodes::Index
target = evaluate(node.target.target, env)
unless target.is_a?(Array) || target.is_a?(String)
raise Runtime::Error, "index assignment on non-indexable"
end
index = evaluate(node.target.index, env)
unless index.is_a?(Integer)
raise Runtime::Error, "index must be an integer: #{index}"
end
if target.is_a?(String)
unless val.is_a?(String)
raise Runtime::Error, "index value must be a string"
end
unless index.between?(-target.length, target.length)
raise Runtime::Error, "index out of range: #{index}"
end
elsif index < -target.length
raise Runtime::Error, "index out of range: #{index}"
end
target[index] = val
else
raise Runtime::Error, "invalid assignment target"
end
val
when Nodes::Const
val = evaluate(node.value, env)
env.define(node.name, val, mutable: false)
val
when Nodes::BinaryOp
left = evaluate(node.left, env)
case node.op
when :and then left && evaluate(node.right, env)
when :or then left || evaluate(node.right, env)
else
right = evaluate(node.right, env)
if %i(+ - * / % < <= > >=).include?(node.op)
unless Types.numeric?(left) && Types.numeric?(right)
raise Runtime::Error, "cannot apply #{node.op} to #{Types.typeof(left)} and #{Types.typeof(right)}"
end
end
if %i(/ %).include?(node.op) && right == 0
raise Runtime::Error, "cannot divide by zero"
end
left.send(node.op, right)
end
when Nodes::UnaryOp
operand = evaluate(node.operand, env)
if node.op == :'-'
unless Types.numeric?(operand)
raise Runtime::Error, "cannot negate #{Types.typeof(operand)}"
end
-operand
else
!operand
end
when Nodes::If
if evaluate(node.cond, env)
evaluate(node.then_body, env)
elsif node.else_body
evaluate(node.else_body, env)
else
end
when Nodes::Switch
val = evaluate(node.value, env)
result = nil
matched = false
node.arms.each do |arm|
if evaluate(arm.pattern, env) == val
result = evaluate(arm.value, env)
matched = true
break
end
end
if !matched && node.else_body
result = evaluate(node.else_body, env)
end
result
when Nodes::Loop
prev = @in_loop
@in_loop = true
begin
loop do
begin
evaluate(node.body, Environment.new(env))
rescue Runtime::Leave
break
end
end
ensure
@in_loop = prev
end
when Nodes::Leave
unless @in_loop
raise Runtime::Error, "leave outside loop"
end
raise Runtime::Leave
when Nodes::Return
result = evaluate(node.value, env)
raise Runtime::Return.new(result)
when Nodes::FunDef
fn = Runtime::Function.new(
node.name, node.params, node.body, env, node.receiver
)
if node.receiver
type_name = node.receiver.type_name
unless env.has?(type_name) && env.get(type_name).is_a?(Runtime::DataType)
raise Runtime::Error, "undefined type: #{type_name}"
end
fn_name = "#{type_name}.#{node.name}"
if @methods[fn_name]
raise Runtime::Error, "already defined method: #{fn_name}"
end
@methods[fn_name] = fn
end
if node.name && node.receiver.nil?
env.define(node.name, fn, mutable: false)
end
fn
when Nodes::Call
args = node.args.map { |a| evaluate(a, env) }
if node.callee.is_a?(Nodes::DotExpr)
if node.callee.target.is_a?(Nodes::Identifier)
key = "#{node.callee.target.name}.#{node.callee.name}"
if fn = @builtins[key]
return fn.call(*args)
end
end
target = evaluate(node.callee.target, env)
unless target.is_a?(Runtime::Instance)
raise Runtime::Error, "method call on non-instance"
end
key = "#{target.type}.#{node.callee.name}"
fn = @methods[key]
unless fn
raise Runtime::Error, "undefined method: #{key}"
end
if args.length != fn.params.length
raise Runtime::Error, arity_error(fn.params.length, args.length)
end
call_env = Environment.new(fn.closure)
call_env.define(fn.receiver.param_name, target, mutable: true)
fn.params.each_with_index do |p, i|
call_env.define(p, args[i], mutable: true)
end
prev = @in_function
@in_function = true
begin
evaluate(fn.body, call_env)
rescue Runtime::Return => ret
ret.value
ensure
@in_function = prev
end
else
callee = evaluate(node.callee, env)
case callee
when Runtime::Function
if args.length != callee.params.length
raise Runtime::Error, arity_error(callee.params.length, args.length)
end
call_env = Environment.new(callee.closure)
callee.params.each_with_index do |p, i|
call_env.define(p, args[i], mutable: true)
end
prev = @in_function
@in_function = true
begin
evaluate(callee.body, call_env)
rescue Runtime::Return => ret
ret.value
ensure
@in_function = prev
end
when Runtime::DataType
fields = {}
if args.length > callee.fields.length
raise Runtime::Error, arity_error("at most #{callee.fields.length}", args.length)
end
callee.fields.each_with_index do |f, i|
fields[f.name] = args.length > i ? args[i] : evaluate(f.default, env)
end
Runtime::Instance.new(callee.name, fields)
else
raise Runtime::Error, "not callable: #{callee.inspect}"
end
end
when Nodes::DotExpr
target = evaluate(node.target, env)
if target.is_a?(Runtime::Instance)
unless target.fields.key?(node.name)
raise Runtime::Error, "undefined field: #{node.name}"
end
target.fields[node.name]
else
raise Runtime::Error, "field access on non-instance"
end
when Nodes::Index
target = evaluate(node.target, env)
index = evaluate(node.index, env)
unless index.is_a?(Integer)
raise Runtime::Error, "index must be an integer: #{index}"
end
unless target.is_a?(Array) || target.is_a?(String)
raise Runtime::Error, "index access on non-indexable"
end
target[index]
when Nodes::DataDef
node.fields.each do |field|
next if field.default.nil?
unless literal?(field.default)
raise Runtime::Error, "field default must be a literal: #{field.name}"
end
end
value = Runtime::DataType.new(node.name, node.fields)
env.define(node.name, value, mutable: false)
value
when Nodes::Try
unless @in_function
raise Runtime::Error, "try outside function"
end
result = evaluate(node.call, env)
unless result.is_a?(Array) && result.length == 2
raise Runtime::Error, "try requires [val, err] tuple"
end
val, err = result
return val if err.nil?
raise Runtime::Return.new(err)
when Nodes::Package then nil
when Nodes::Import then nil
when nil then nil
else
raise Runtime::Error, "unknown node: #{node.class}"
end
end
|