rbsiev 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +57 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +90 -0
- data/Rakefile +20 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/rbsiev +51 -0
- data/lib/rbsiev.rb +98 -0
- data/lib/rbsiev/environment.rb +110 -0
- data/lib/rbsiev/error.rb +5 -0
- data/lib/rbsiev/evaluator.rb +330 -0
- data/lib/rbsiev/primitives.rb +88 -0
- data/lib/rbsiev/primitives/arithmetic.rb +54 -0
- data/lib/rbsiev/primitives/comparison.rb +46 -0
- data/lib/rbsiev/primitives/empty_list.rb +15 -0
- data/lib/rbsiev/printer.rb +19 -0
- data/lib/rbsiev/procedure.rb +47 -0
- data/lib/rbsiev/repl.rb +112 -0
- data/lib/rbsiev/version.rb +6 -0
- data/lib/rubasteme/ast/misc.rb +313 -0
- data/lib/scmo/object.rb +117 -0
- data/rbsiev.gemspec +31 -0
- metadata +100 -0
data/lib/rbsiev/error.rb
ADDED
@@ -0,0 +1,330 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rbsiev
|
4
|
+
class Evaluator
|
5
|
+
include Rubasteme::AST::Misc
|
6
|
+
|
7
|
+
def self.version
|
8
|
+
"(rbsiev.evaluator :version #{VERSION} :release #{RELEASE})"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@verbose = false
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :verbose
|
16
|
+
|
17
|
+
def eval(ast_node, env)
|
18
|
+
self.send(func_map(ast_node.type), ast_node, env)
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply(procedure, arguments)
|
22
|
+
case procedure.type
|
23
|
+
when :procedure_primitive
|
24
|
+
procedure.apply(arguments)
|
25
|
+
when :procedure_compound
|
26
|
+
apply_compound_procedure(procedure, arguments)
|
27
|
+
else
|
28
|
+
raise Error, "Unknown procedure type -- APPLY: got=%s" % procedure.type.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
EV_FUNCTIONS_MAP = {
|
35
|
+
ast_empty_list: :eval_empty_list,
|
36
|
+
ast_boolean: :eval_boolean,
|
37
|
+
ast_identifier: :eval_variable,
|
38
|
+
ast_character: :eval_self_evaluating,
|
39
|
+
ast_string: :eval_self_evaluating,
|
40
|
+
ast_number: :eval_self_evaluating,
|
41
|
+
ast_program: :eval_program,
|
42
|
+
ast_quotation: :eval_quoted,
|
43
|
+
ast_procedure_call: :eval_combination,
|
44
|
+
ast_lambda_expression: :eval_lambda,
|
45
|
+
ast_conditional: :eval_if,
|
46
|
+
ast_assignment: :eval_assignment,
|
47
|
+
ast_identifier_definition: :eval_definition,
|
48
|
+
ast_cond: :eval_cond,
|
49
|
+
ast_and: :eval_and,
|
50
|
+
ast_or: :eval_or,
|
51
|
+
ast_when: :eval_when,
|
52
|
+
ast_unless: :eval_unless,
|
53
|
+
ast_let: :eval_let,
|
54
|
+
ast_let_star: :eval_let_star,
|
55
|
+
ast_letrec: :eval_letrec,
|
56
|
+
ast_letrec_star: :eval_letrec_star,
|
57
|
+
ast_begin: :eval_begin,
|
58
|
+
ast_do: :eval_do,
|
59
|
+
}
|
60
|
+
|
61
|
+
def func_map(ast_node_type)
|
62
|
+
func = EV_FUNCTIONS_MAP[ast_node_type]
|
63
|
+
if func.nil?
|
64
|
+
raise Error, "Unknown expression type -- EVAL: got=%s" % ast_node_type
|
65
|
+
end
|
66
|
+
func
|
67
|
+
end
|
68
|
+
|
69
|
+
def eval_empty_list(_ast_node, _env)
|
70
|
+
SCM_EMPTY_LIST
|
71
|
+
end
|
72
|
+
|
73
|
+
def eval_boolean(ast_node, _)
|
74
|
+
case ast_node.literal
|
75
|
+
when /\A#f(alse)?\Z/
|
76
|
+
false
|
77
|
+
when /\A#t(rue)?\Z/
|
78
|
+
true
|
79
|
+
else
|
80
|
+
raise Error, "Invalid boolean literal -- EVAL: got=%s" % exp[1]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def eval_variable(ast_node, env)
|
85
|
+
env.lookup_variable_value(identifier(ast_node))
|
86
|
+
end
|
87
|
+
|
88
|
+
def eval_self_evaluating(ast_node, env)
|
89
|
+
if ast_node.type == :ast_number and /([^\/]+)\/([^\/]+)/ === ast_node.literal
|
90
|
+
md = Regexp.last_match
|
91
|
+
Kernel.eval("Rational(#{md[1]}, #{md[2]})")
|
92
|
+
else
|
93
|
+
Kernel.eval(ast_node.literal)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def eval_program(ast_node, env)
|
98
|
+
result = nil
|
99
|
+
ast_node.each { |node|
|
100
|
+
result = self.eval(node, env)
|
101
|
+
}
|
102
|
+
result
|
103
|
+
end
|
104
|
+
|
105
|
+
def eval_quoted(ast_node, _env)
|
106
|
+
text_of_quotation(ast_node)
|
107
|
+
end
|
108
|
+
|
109
|
+
def eval_combination(ast_node, env)
|
110
|
+
apply(self.eval(ast_node.operator, env),
|
111
|
+
list_of_values(ast_node.operands, env))
|
112
|
+
end
|
113
|
+
|
114
|
+
def eval_lambda(ast_node, env)
|
115
|
+
parameters = ast_node.formals.map{|e| identifier(e)}
|
116
|
+
dummy_arguments = Array.new(parameters.size, :ev_unassigned)
|
117
|
+
extended_env = env.extend(parameters, dummy_arguments)
|
118
|
+
|
119
|
+
definitions = ast_node.body.definitions
|
120
|
+
unless definitions.empty?
|
121
|
+
names = []
|
122
|
+
procedures = []
|
123
|
+
definitions.each { |definition|
|
124
|
+
name = identifier(definition.identifier)
|
125
|
+
extended_env.define_variable(name, :ev_unassigned)
|
126
|
+
|
127
|
+
self.eval(definition, extended_env)
|
128
|
+
|
129
|
+
names << name
|
130
|
+
procedures << extended_env.lookup_variable_value(name)
|
131
|
+
}
|
132
|
+
|
133
|
+
target_frame = extended_env.first_frame
|
134
|
+
names.zip(procedures).each { |name, procedure|
|
135
|
+
target_frame.set(name, procedure)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
make_procedure(ast_node.formals, ast_node.body.sequence, extended_env)
|
140
|
+
end
|
141
|
+
|
142
|
+
def eval_if(ast_node, env)
|
143
|
+
test_result = self.eval(ast_node.test, env)
|
144
|
+
if true?(test_result)
|
145
|
+
self.eval(ast_node.consequent, env)
|
146
|
+
else
|
147
|
+
self.eval(ast_node.alternate, env) if ast_node.alternate?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def eval_assignment(ast_node, env)
|
152
|
+
var = identifier(ast_node.identifier)
|
153
|
+
val = self.eval(ast_node.expression, env)
|
154
|
+
env.set_variable_value(var, val)
|
155
|
+
end
|
156
|
+
|
157
|
+
def eval_definition(ast_node, env)
|
158
|
+
var = identifier(ast_node.identifier)
|
159
|
+
val = self.eval(ast_node.expression, env)
|
160
|
+
env.define_variable(var, val)
|
161
|
+
end
|
162
|
+
|
163
|
+
def eval_cond(ast_node, env)
|
164
|
+
eval_if(cond_to_if(ast_node), env)
|
165
|
+
end
|
166
|
+
|
167
|
+
def eval_and(ast_node, env)
|
168
|
+
if empty_node?(ast_node)
|
169
|
+
SCM_TRUE
|
170
|
+
else
|
171
|
+
result = self.eval(first_node(ast_node), env)
|
172
|
+
if true?(result)
|
173
|
+
if last_node?(ast_node)
|
174
|
+
result
|
175
|
+
else
|
176
|
+
self.eval_and(rest_nodes(ast_node), env)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
SCM_FALSE
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def eval_or(ast_node, env)
|
185
|
+
if empty_node?(ast_node)
|
186
|
+
SCM_FALSE
|
187
|
+
else
|
188
|
+
result = self.eval(first_node(ast_node), env)
|
189
|
+
if true?(result)
|
190
|
+
result
|
191
|
+
else
|
192
|
+
if last_node?(ast_node)
|
193
|
+
SCM_FALSE
|
194
|
+
else
|
195
|
+
self.eval_or(rest_nodes(ast_node), env)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def eval_when(ast_node, env)
|
202
|
+
test_result = self.eval(ast_node.test, env)
|
203
|
+
if true?(test_result)
|
204
|
+
self.eval_sequence(ast_node.sequence, env)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def eval_unless(ast_node, env)
|
209
|
+
test_result = self.eval(ast_node.test, env)
|
210
|
+
unless true?(test_result)
|
211
|
+
self.eval_sequence(ast_node.sequence, env)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def eval_let(ast_node, env)
|
216
|
+
combination = let_to_combination(ast_node)
|
217
|
+
|
218
|
+
# named let
|
219
|
+
if ast_node.identifier
|
220
|
+
name = identifier(ast_node.identifier)
|
221
|
+
extended_env = env.extend([name], [:ev_unassigned])
|
222
|
+
procedure = self.eval(combination.operator, extended_env)
|
223
|
+
extended_env.set_variable_value(name, procedure)
|
224
|
+
env = extended_env
|
225
|
+
combination.operator = make_identifier(name)
|
226
|
+
end
|
227
|
+
|
228
|
+
self.eval(combination, env)
|
229
|
+
end
|
230
|
+
|
231
|
+
def eval_let_star(ast_node, env)
|
232
|
+
nested_lets = let_star_to_nested_lets(ast_node.bindings,
|
233
|
+
ast_node.body)
|
234
|
+
self.eval(nested_lets, env)
|
235
|
+
end
|
236
|
+
|
237
|
+
def eval_letrec(ast_node, env)
|
238
|
+
combination = let_to_combination(ast_node)
|
239
|
+
|
240
|
+
params = parameters(combination.operator.formals)
|
241
|
+
ext_env = env.extend(params, Array.new(params.size, :ev_unassigned))
|
242
|
+
|
243
|
+
target_frame = ext_env.first_frame
|
244
|
+
|
245
|
+
operands = combination.operands.map{|e| self.eval(e, ext_env)}
|
246
|
+
params.zip(operands).each { |parameter, arg|
|
247
|
+
target_frame.set(parameter, arg)
|
248
|
+
}
|
249
|
+
|
250
|
+
self.eval(combination, ext_env)
|
251
|
+
end
|
252
|
+
|
253
|
+
def eval_letrec_star(ast_node, env)
|
254
|
+
combination = let_to_combination(ast_node)
|
255
|
+
|
256
|
+
params = parameters(combination.operator.formals)
|
257
|
+
ext_env = env.extend(params, Array.new(params.size, :ev_unassigned))
|
258
|
+
|
259
|
+
target_frame = ext_env.first_frame
|
260
|
+
|
261
|
+
params.zip(combination.operands).each { |parameter, operand|
|
262
|
+
arg = self.eval(operand, ext_env)
|
263
|
+
target_frame.set(parameter, arg)
|
264
|
+
}
|
265
|
+
|
266
|
+
self.eval(combination, ext_env)
|
267
|
+
end
|
268
|
+
|
269
|
+
def eval_begin(ast_node, env)
|
270
|
+
eval_sequence(ast_node.sequence, env)
|
271
|
+
end
|
272
|
+
|
273
|
+
def eval_sequence(ast_node, env)
|
274
|
+
value = nil
|
275
|
+
ast_node.each{|exp| value = self.eval(exp, env)}
|
276
|
+
value
|
277
|
+
end
|
278
|
+
|
279
|
+
EV_DO_LOOP_NAME = "ev_do_loop"
|
280
|
+
|
281
|
+
def eval_do(ast_node, env)
|
282
|
+
let_node = do_to_named_let(ast_node, EV_DO_LOOP_NAME)
|
283
|
+
self.eval(let_node, env)
|
284
|
+
end
|
285
|
+
|
286
|
+
def text_of_quotation(ast_node)
|
287
|
+
"not implemented yet"
|
288
|
+
end
|
289
|
+
|
290
|
+
def true?(obj)
|
291
|
+
case obj
|
292
|
+
when Scmo::Object
|
293
|
+
obj.to_rb == false ? false : true
|
294
|
+
when Rubasteme::AST::BooleanNode
|
295
|
+
self.eval_boolean(obj, nil)
|
296
|
+
when FalseClass, NilClass
|
297
|
+
false
|
298
|
+
when TrueClass
|
299
|
+
true
|
300
|
+
else
|
301
|
+
true
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def make_procedure(parameters, seq, env)
|
306
|
+
Procedure.make_procedure(parameters, seq, env)
|
307
|
+
end
|
308
|
+
|
309
|
+
def list_of_values(ast_nodes, env)
|
310
|
+
ast_nodes.map{|node| self.eval(node, env)}
|
311
|
+
end
|
312
|
+
|
313
|
+
def apply_compound_procedure(procedure, arguments)
|
314
|
+
env = procedure.env
|
315
|
+
procedure_parameters(procedure).zip(arguments).each { |var, val|
|
316
|
+
env.set_variable_value(var, val)
|
317
|
+
}
|
318
|
+
eval_sequence(procedure_body(procedure), env)
|
319
|
+
end
|
320
|
+
|
321
|
+
def procedure_parameters(procedure)
|
322
|
+
procedure.parameters.map{|node| identifier(node)}
|
323
|
+
end
|
324
|
+
|
325
|
+
def procedure_body(procedure)
|
326
|
+
procedure.body
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rbsiev
|
4
|
+
|
5
|
+
PRIMITIVE_NAMES_MAP = {
|
6
|
+
# Some primitive procedure names in Scheme must be defined with
|
7
|
+
# different names in Ruby.
|
8
|
+
"+" => :add,
|
9
|
+
"-" => :subtract,
|
10
|
+
"*" => :mul,
|
11
|
+
"/" => :div,
|
12
|
+
"%" => :mod,
|
13
|
+
"<" => :lt?,
|
14
|
+
"<=" => :le?,
|
15
|
+
">" => :gt?,
|
16
|
+
">=" => :ge?,
|
17
|
+
"=" => :same_value?,
|
18
|
+
}
|
19
|
+
|
20
|
+
module Primitives
|
21
|
+
|
22
|
+
require_relative "primitives/empty_list"
|
23
|
+
require_relative "primitives/comparison"
|
24
|
+
require_relative "primitives/arithmetic"
|
25
|
+
|
26
|
+
include EmptyList
|
27
|
+
include Comparison
|
28
|
+
include Arithmetic
|
29
|
+
|
30
|
+
def cons(scm_obj1, scm_obj2)
|
31
|
+
[scm_obj1, scm_obj2]
|
32
|
+
end
|
33
|
+
|
34
|
+
def pair?(scm_obj)
|
35
|
+
scm_obj.instance_of?(Array)
|
36
|
+
end
|
37
|
+
|
38
|
+
def list?(scm_obj)
|
39
|
+
scm_obj.instance_of?(Array)
|
40
|
+
end
|
41
|
+
|
42
|
+
def car(scm_list)
|
43
|
+
scm_list[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def cdr(scm_list)
|
47
|
+
scm_list[1..-1]
|
48
|
+
end
|
49
|
+
|
50
|
+
def list(*scm_objs)
|
51
|
+
scm_objs
|
52
|
+
end
|
53
|
+
|
54
|
+
def append(*scm_lists)
|
55
|
+
if scm_lists.empty?
|
56
|
+
SCM_EMPTY_LIST
|
57
|
+
else
|
58
|
+
scm_lists[0] + append(*scm_lists[1..-1])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def write(scm_obj)
|
63
|
+
print scm_obj
|
64
|
+
end
|
65
|
+
|
66
|
+
def display(scm_obj)
|
67
|
+
write(scm_obj)
|
68
|
+
print "\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
def number?(scm_obj)
|
72
|
+
scm_obj.kind_of?(Numeric) ? SCM_TRUE : SCM_FALSE
|
73
|
+
end
|
74
|
+
|
75
|
+
# :stopdoc:
|
76
|
+
|
77
|
+
# Registers primitive procedure names into the name map, those
|
78
|
+
# names are identical in Scheme and Ruby.
|
79
|
+
instance_methods(true).each { |sym|
|
80
|
+
name = sym.to_s
|
81
|
+
unless PRIMITIVE_NAMES_MAP.key?(name)
|
82
|
+
PRIMITIVE_NAMES_MAP[name] = sym
|
83
|
+
end
|
84
|
+
}
|
85
|
+
# :startdoc:
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rbsiev
|
4
|
+
|
5
|
+
module Primitives
|
6
|
+
|
7
|
+
module Arithmetic
|
8
|
+
|
9
|
+
def zero?(obj)
|
10
|
+
if number?(obj) == SCM_TRUE
|
11
|
+
same_value?(obj, 0)
|
12
|
+
else
|
13
|
+
SCM_FALSE
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(*args)
|
18
|
+
a_calc(:+, *args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def subtract(*args)
|
22
|
+
a_calc(:-, *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def mul(*args)
|
26
|
+
a_calc(:*, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def div(*args)
|
30
|
+
a_calc(:/, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def mod(*args)
|
34
|
+
a_calc(:%, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def a_calc(op, *args)
|
40
|
+
case args.size
|
41
|
+
when 0
|
42
|
+
0
|
43
|
+
when 1
|
44
|
+
args[0]
|
45
|
+
else
|
46
|
+
a_calc(op, args[0].send(op, args[1]), *args[2..-1])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end # end of Arithmetic
|
51
|
+
|
52
|
+
end # end of Primitives
|
53
|
+
|
54
|
+
end
|