rbsiev 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|