rubasteme 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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