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.
- 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 +136 -0
- data/Rakefile +20 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/examples/mini_rus3 +193 -0
- data/examples/mini_sicp_scheme +62 -0
- data/examples/sicp_scheme/environment.rb +103 -0
- data/examples/sicp_scheme/error.rb +5 -0
- data/examples/sicp_scheme/evaluator.rb +312 -0
- data/examples/sicp_scheme/primitives.rb +123 -0
- data/examples/sicp_scheme/printer.rb +12 -0
- data/exe/rubasteme +88 -0
- data/lib/rubasteme.rb +16 -0
- data/lib/rubasteme/ast.rb +93 -0
- data/lib/rubasteme/ast/branch_node.rb +514 -0
- data/lib/rubasteme/ast/leaf_node.rb +62 -0
- data/lib/rubasteme/error.rb +50 -0
- data/lib/rubasteme/parser.rb +608 -0
- data/lib/rubasteme/utils.rb +14 -0
- data/lib/rubasteme/version.rb +6 -0
- data/rubasteme.gemspec +31 -0
- metadata +88 -0
@@ -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,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
|