rubasteme 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.
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "readline"
5
+ require "rubasteme"
6
+
7
+ def usage
8
+ puts <<HELP
9
+ usage:
10
+ sicp_scheme [option]
11
+ option:
12
+ -v, --version : print version
13
+ -h, --help : show this message
14
+ HELP
15
+ end
16
+
17
+ module SicpScheme
18
+ require_relative "sicp_scheme/error"
19
+ require_relative "sicp_scheme/environment"
20
+ require_relative "sicp_scheme/primitives"
21
+ require_relative "sicp_scheme/evaluator"
22
+ require_relative "sicp_scheme/printer"
23
+
24
+ TOPLEVEL_ENV = Environment.empty_environment
25
+
26
+ def self.repl(prompt)
27
+ env = TOPLEVEL_ENV
28
+ evaluator = Evaluator.new
29
+ printer = Printer.new
30
+
31
+ msg = loop {
32
+ source = Readline::readline(prompt, true)
33
+ break "Bye!" if source.nil?
34
+
35
+ case source
36
+ when /\(load\s+"(.*)"\)/, /\(load-scm\s+"(.*)"\)/
37
+ file = Regexp.last_match[1]
38
+ source = File.readlines(file, chomp: true).join(" ")
39
+ when /\(version\)/
40
+ puts evaluator.version
41
+ puts "(Rubasteme :version #{Rubasteme::VERSION} :release #{Rubasteme::RELEASE})"
42
+ puts "(Rbscmlex :version #{Rbscmlex::VERSION} :release #{Rbscmlex::RELEASE})"
43
+ next
44
+ end
45
+
46
+ lexer = Rbscmlex::Lexer.new(source, form: :token)
47
+ parser = Rubasteme.parser(lexer)
48
+ exp = parser.parse.to_a
49
+
50
+ result = evaluator.eval(exp, env)
51
+
52
+ printer.print(result)
53
+ }
54
+
55
+ msg
56
+ end
57
+ end
58
+
59
+ prompt = "SICP Scheme> "
60
+ msg = SicpScheme.repl(prompt)
61
+
62
+ puts msg unless msg.nil?
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SicpScheme
4
+
5
+ class Environment
6
+ def self.empty_environment
7
+ Environment.new(nil)
8
+ end
9
+
10
+ def initialize(base_env = nil)
11
+ @frame = nil
12
+ @enclosing_environment = base_env
13
+ end
14
+
15
+ attr_reader :frame
16
+ attr_reader :enclosing_environment
17
+
18
+ def extend(vars, vals)
19
+ if vars.size < vals.size
20
+ raise Error, "Too many arguments supplied: %s => %s" % [vars, vals]
21
+ elsif vars.size > vals.size
22
+ raise Error, "Too few arguments supplied: %s => %s" % [vars, vals]
23
+ else
24
+ new_env = Environment.new(self)
25
+ new_env.make_frame(vars, vals)
26
+ new_env
27
+ end
28
+ end
29
+
30
+ def lookup_variable_value(var)
31
+ value, _ = lookup_value_and_env_defined_var(var)
32
+ value
33
+ end
34
+
35
+ def define_variable(var, val)
36
+ if @frame
37
+ @frame[var] = val
38
+ else
39
+ @frame = make_frame([var], [val])
40
+ end
41
+ val
42
+ end
43
+
44
+ def set_variable(var, val)
45
+ _, env = lookup_value_and_env_defined_var(var)
46
+ if env
47
+ env.frame[var] = val
48
+ else
49
+ raise Error, "Unbound variable: got=%s" % var
50
+ end
51
+ end
52
+
53
+ def make_frame(variables, values)
54
+ @frame = Frame.new(variables, values)
55
+ end
56
+
57
+ class Frame
58
+ def initialize(variables, values)
59
+ @bindings = variables.zip(values).to_h
60
+ end
61
+
62
+ def defined?(var)
63
+ @bindings.key?(var)
64
+ end
65
+
66
+ def [](var)
67
+ @bindings[var]
68
+ end
69
+
70
+ def []=(var, val)
71
+ @bindings[var] = val
72
+ end
73
+
74
+ def variables
75
+ @bindings.keys
76
+ end
77
+
78
+ def values
79
+ @bindings.values
80
+ end
81
+
82
+ def add_binding(var, val)
83
+ @bindings[var] = val
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def lookup_value_and_env_defined_var(var)
90
+ value = nil
91
+ env = self
92
+ while env
93
+ if env.frame && env.frame.defined?(var)
94
+ value = env.frame[var]
95
+ break
96
+ end
97
+ env = env.enclosing_environment
98
+ end
99
+ [value, env]
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SicpScheme
4
+ class Error < StandardError; end
5
+ end
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SicpScheme
4
+
5
+ # An evaluator which can evaluate the subset of syntax and procedures
6
+ # of Scheme language. It is derived from SICP Chatpter 4.
7
+
8
+ class Evaluator
9
+ include Primitives
10
+
11
+ def version # :nodoc:
12
+ ver = "0.1.0"
13
+ rel = "2021-05-20"
14
+ "(SICP-evaluator :version #{ver} :release #{rel})"
15
+ end
16
+
17
+ def initialize
18
+ @ver = version
19
+ end
20
+
21
+ def eval(exp, env)
22
+ case tag(exp)
23
+ when :ast_program
24
+ result = nil
25
+ cdr(exp).each { |node|
26
+ result = self.eval(node, env)
27
+ }
28
+ result
29
+ when :ast_empty_list
30
+ []
31
+ when :ast_boolean
32
+ eval_boolean(exp, env)
33
+ when *EV_SELF_EVALUATING
34
+ eval_self_evaluating(exp, env)
35
+ when *EV_VARIABLE
36
+ lookup_variable_value(exp[1], env)
37
+ when *EV_QUOTED
38
+ text_of_quotation(exp)
39
+ when *EV_ASSIGNMENT
40
+ eval_assignment(exp, env)
41
+ when *EV_DEFINITION
42
+ eval_definition(exp, env)
43
+ when *EV_IF
44
+ eval_if(exp, env)
45
+ when *EV_LAMBDA
46
+ make_procedure(lambda_parameters(exp),
47
+ lambda_body(exp),
48
+ env)
49
+ when *EV_BEGIN
50
+ eval_sequence(begin_actions(exp), env)
51
+ when *EV_COND
52
+ eval_if(cond_to_if(exp), env)
53
+ when *EV_APPLICATION
54
+ apply(self.eval(operator(exp), env),
55
+ list_of_values(operands(exp), env))
56
+ else
57
+ raise Error, "Unknown expression type -- EVAL: got=%s" % tag(exp)
58
+ end
59
+ end
60
+
61
+ def apply(procedure, arguments)
62
+ if primitive_procedure?(procedure)
63
+ apply_primitive_procedure(procedure, arguments)
64
+ elsif compound_procedure?(procedure)
65
+ apply_compound_procedure(procedure, arguments)
66
+ else
67
+ raise Error, "Unknown procedure type -- APPLY: got=%s" % procedure.to_s
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def empty_env
74
+ Kernel.binding
75
+ end
76
+
77
+ EV_SELF_EVALUATING = [:ast_string, :ast_number,]
78
+ EV_VARIABLE = [:ast_identifier]
79
+ EV_QUOTED = [:ast_quotation]
80
+ EV_ASSIGNMENT = [:ast_assignment]
81
+ EV_DEFINITION = [:ast_identifier_definition]
82
+ EV_IF = [:ast_conditional]
83
+ EV_LAMBDA = [:ast_lambda_expression]
84
+ EV_BEGIN = [:ast_begin]
85
+ EV_COND = [:ast_cond]
86
+ EV_APPLICATION = [:ast_procedure_call]
87
+
88
+ def tagged?(exp)
89
+ exp.instance_of?(Array) and exp.size > 1 and exp[0].instance_of?(Symbol)
90
+ end
91
+
92
+ def tag(exp)
93
+ exp[0] if tagged?(exp)
94
+ end
95
+
96
+ def package(exp)
97
+ exp[1..-1] if tagged?(exp)
98
+ end
99
+
100
+ def identifier(exp)
101
+ if tagged?(exp) and tag(exp) == :ast_identifier
102
+ exp[1]
103
+ end
104
+ end
105
+
106
+ def lookup_variable_value(var, env)
107
+ val = env.lookup_variable_value(var)
108
+ if val.nil?
109
+ rb_name = scm2rb_name(var).intern
110
+ val = method(rb_name) if respond_to?(rb_name)
111
+ end
112
+ raise Error, "Unbound variable: got=%s" % var if val.nil?
113
+ val
114
+ end
115
+
116
+ SCM2RB_OPS_MAP = {
117
+ "+" => "add",
118
+ "-" => "subtract",
119
+ "*" => "mul",
120
+ "/" => "div",
121
+ "%" => "mod",
122
+ "=" => "eqv?",
123
+ "<" => "lt?",
124
+ ">" => "gt?",
125
+ "<=" => "le?",
126
+ ">=" => "ge?",
127
+ }
128
+
129
+ def scm2rb_name(scm_name)
130
+ scm_name.sub(/\A([+\-\*\/%=])|([<>]=?)\Z/, SCM2RB_OPS_MAP)
131
+ end
132
+
133
+ def eval_self_evaluating(exp, env)
134
+ case tag(exp)
135
+ when :ast_number
136
+ case exp[1]
137
+ when /([^\/]+)\/([^\/]+)/
138
+ md = Regexp.last_match
139
+ Kernel.eval("Rational(md[1], md[2])")
140
+ else
141
+ Kernel.eval(exp[1])
142
+ end
143
+ else
144
+ Kernel.eval(exp[1])
145
+ end
146
+ end
147
+
148
+ def eval_assignment(exp, env)
149
+ # exp = [:ast_assignment, [:ast_identifier], [:ast_*]]
150
+ var = assignment_variable(exp)
151
+ val = self.eval(assignment_value(exp), env)
152
+ env.set_variable(var, val)
153
+ val
154
+ end
155
+
156
+ def assignment_variable(exp)
157
+ # exp[1] = [:ast_identifier, <literal>]
158
+ exp[1][1]
159
+ end
160
+
161
+ def assignment_value(exp)
162
+ exp[2]
163
+ end
164
+
165
+ def operator(exp)
166
+ # exp = [:ast_procedure_call, [:ast_*_1], [:ast_*_2], [:ast_*_3], ...]
167
+ # :ast_*_1 must be :ast_identifier or :ast_lambda_expression
168
+ exp[1]
169
+ end
170
+
171
+ def operands(exp)
172
+ # exp = [:ast_procedure_call, [:ast_*_1], [:ast_*_2], [:ast_*_3], ...]
173
+ exp[2..-1]
174
+ end
175
+
176
+ def list_of_values(exps, env)
177
+ exps.map{|e| self.eval(e, env)}
178
+ end
179
+
180
+ def primitive_procedure?(procedure)
181
+ procedure.instance_of?(Method)
182
+ end
183
+
184
+ def compound_procedure?(procedure)
185
+ tagged?(procedure) and tag(procedure) == :sicp_scheme_procedure
186
+ end
187
+
188
+ def apply_primitive_procedure(procedure, arguments)
189
+ if procedure.instance_of?(Method)
190
+ procedure.call(*arguments)
191
+ end
192
+ end
193
+
194
+ def apply_compound_procedure(procedure, arguments)
195
+ base_env = procedure_environment(procedure)
196
+ extended_env = base_env.extend(procedure_parameters(procedure),
197
+ arguments)
198
+ eval_sequence(procedure_body(procedure), extended_env)
199
+ end
200
+
201
+ def begin_actions(exp)
202
+ # exp = [:ast_begin, [:ast_*_1], [:ast_*_2], ... ]
203
+ exp[1..-1]
204
+ end
205
+
206
+ def eval_sequence(exps, env)
207
+ if last_exp?(exps)
208
+ self.eval(first_exp(exps), env)
209
+ else
210
+ self.eval(first_exp(exps), env)
211
+ eval_sequence(rest_exps(exps), env)
212
+ end
213
+ end
214
+
215
+ def last_exp?(exps)
216
+ exps.instance_of?(Array) and exps.size == 1
217
+ end
218
+
219
+ def first_exp(exps)
220
+ exps.instance_of?(Array) and exps[0]
221
+ end
222
+
223
+ def rest_exps(exps)
224
+ exps.instance_of?(Array) and exps[1..-1]
225
+ end
226
+
227
+ def eval_definition(exp, env)
228
+ var = definition_variable(exp)
229
+ val = self.eval(definition_value(exp), env)
230
+ env.define_variable(var, val)
231
+ var
232
+ end
233
+
234
+ def definition_variable(exp)
235
+ # exp = [:ast_identifier_definition, [:ast_identifier], [:ast_*]]
236
+ identifier(exp[1])
237
+ end
238
+
239
+ def definition_value(exp)
240
+ # exp = [:ast_identifier_definition, [:ast_identifier], [:ast_*]]
241
+ exp[2]
242
+ end
243
+
244
+ def lambda_parameters(exp)
245
+ # exp = [:ast_lambda_expression, [:ast_formals], [:ast_*_1] ...]
246
+ formals = exp[1][1..-1]
247
+ formals.map{|node| identifier(node)}
248
+ end
249
+
250
+ def lambda_body(exp)
251
+ # exp = [:ast_lambda_expression, [:ast_formals], [:ast_*_1] ...]
252
+ exp[2..-1]
253
+ end
254
+
255
+ def make_procedure(parameters, body, env)
256
+ # parameters = [:ast_formals, [:ast_identifier_1], [:ast_identifier_2] ...]
257
+ # body = [[:ast_*_1], [:ast_*_2], ...]
258
+ [:sicp_scheme_procedure, parameters, body, env]
259
+ end
260
+
261
+ def procedure_parameters(procedure)
262
+ compound_procedure?(procedure) and procedure[1]
263
+ end
264
+
265
+ def procedure_body(procedure)
266
+ compound_procedure?(procedure) and procedure[2]
267
+ end
268
+
269
+ def procedure_environment(procedure)
270
+ compound_procedure?(procedure) and procedure[3]
271
+ end
272
+
273
+ def eval_if(exp, env)
274
+ if true?(self.eval(if_predicate(exp), env))
275
+ self.eval(if_consequent(exp), env)
276
+ else
277
+ self.eval(if_alternative(exp), env)
278
+ end
279
+ end
280
+
281
+ def if_predicate(exp)
282
+ # exp = [:ast_conditional, [:ast_*], [:ast_*], [:ast_*]]
283
+ # predicate consequent alternative
284
+ exp[1]
285
+ end
286
+
287
+ def if_consequent(exp)
288
+ exp[2]
289
+ end
290
+
291
+ def if_alternative(exp)
292
+ exp[3]
293
+ end
294
+
295
+ def true?(exp)
296
+ self.eval_boolean(exp, nil)
297
+ end
298
+
299
+ def eval_boolean(exp, _ = nil)
300
+ case exp[1]
301
+ when /\A#f(alse)?\Z/
302
+ false
303
+ when /\A#t(rue)?\Z/
304
+ true
305
+ else
306
+ raise Error, "Invalid boolean literal -- EVAL: got=%s" % exp[1]
307
+ end
308
+ end
309
+
310
+ end
311
+
312
+ end