heist 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.
- data/History.txt +21 -0
- data/Manifest.txt +53 -0
- data/README.txt +274 -0
- data/Rakefile +12 -0
- data/bin/heist +16 -0
- data/lib/bin_spec.rb +25 -0
- data/lib/builtin/library.scm +95 -0
- data/lib/builtin/primitives.rb +306 -0
- data/lib/builtin/syntax.rb +166 -0
- data/lib/builtin/syntax.scm +155 -0
- data/lib/heist.rb +47 -0
- data/lib/parser/nodes.rb +105 -0
- data/lib/parser/scheme.rb +1081 -0
- data/lib/parser/scheme.tt +80 -0
- data/lib/repl.rb +112 -0
- data/lib/runtime/binding.rb +31 -0
- data/lib/runtime/callable/continuation.rb +24 -0
- data/lib/runtime/callable/function.rb +55 -0
- data/lib/runtime/callable/macro.rb +170 -0
- data/lib/runtime/callable/macro/expansion.rb +15 -0
- data/lib/runtime/callable/macro/matches.rb +77 -0
- data/lib/runtime/callable/macro/splice.rb +56 -0
- data/lib/runtime/data/expression.rb +23 -0
- data/lib/runtime/data/identifier.rb +20 -0
- data/lib/runtime/data/list.rb +36 -0
- data/lib/runtime/frame.rb +118 -0
- data/lib/runtime/runtime.rb +61 -0
- data/lib/runtime/scope.rb +121 -0
- data/lib/runtime/stack.rb +60 -0
- data/lib/runtime/stackless.rb +49 -0
- data/lib/stdlib/benchmark.scm +12 -0
- data/lib/stdlib/birdhouse.scm +82 -0
- data/test/arithmetic.scm +57 -0
- data/test/benchmarks.scm +27 -0
- data/test/booleans.scm +6 -0
- data/test/closures.scm +16 -0
- data/test/conditionals.scm +55 -0
- data/test/continuations.scm +144 -0
- data/test/define_functions.scm +27 -0
- data/test/define_values.scm +28 -0
- data/test/delay.scm +8 -0
- data/test/file_loading.scm +9 -0
- data/test/hygienic.scm +39 -0
- data/test/let.scm +42 -0
- data/test/lib.scm +2 -0
- data/test/macro-helpers.scm +19 -0
- data/test/macros.scm +343 -0
- data/test/numbers.scm +19 -0
- data/test/plt-macros.txt +40 -0
- data/test/test_heist.rb +84 -0
- data/test/unhygienic.scm +11 -0
- data/test/vars.scm +2 -0
- metadata +138 -0
@@ -0,0 +1,306 @@
|
|
1
|
+
# Functions that create new functions
|
2
|
+
|
3
|
+
# (define) binds values to names in the current scope.
|
4
|
+
# If the first parameter is a list it creates a function,
|
5
|
+
# otherwise it eval's the second parameter and binds it
|
6
|
+
# to the name given by the first.
|
7
|
+
syntax('define') do |scope, names, *body|
|
8
|
+
List === names ?
|
9
|
+
scope.define(names.first, names.rest, body) :
|
10
|
+
scope[names] = Heist.evaluate(body.first, scope)
|
11
|
+
end
|
12
|
+
|
13
|
+
# (lambda) returns an anonymous function whose arguments
|
14
|
+
# are named by the first parameter and whose body is given
|
15
|
+
# by the remaining parameters.
|
16
|
+
syntax('lambda') do |scope, names, *body|
|
17
|
+
Function.new(scope, names, body)
|
18
|
+
end
|
19
|
+
|
20
|
+
# (set!) reassigns the value of an existing bound variable,
|
21
|
+
# in the innermost scope responsible for binding it.
|
22
|
+
syntax('set!') do |scope, name, value|
|
23
|
+
scope.set!(name, Heist.evaluate(value, scope))
|
24
|
+
end
|
25
|
+
|
26
|
+
#----------------------------------------------------------------
|
27
|
+
|
28
|
+
# Macros
|
29
|
+
|
30
|
+
syntax('define-syntax') do |scope, name, transformer|
|
31
|
+
scope[name] = Heist.evaluate(transformer, scope)
|
32
|
+
end
|
33
|
+
|
34
|
+
syntax('let-syntax') do |*args|
|
35
|
+
call('let', *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
syntax('letrec-syntax') do |*args|
|
39
|
+
call('letrec', *args)
|
40
|
+
end
|
41
|
+
|
42
|
+
syntax('syntax-rules') do |scope, keywords, *rules|
|
43
|
+
Macro.new(scope, keywords, rules)
|
44
|
+
end
|
45
|
+
|
46
|
+
#----------------------------------------------------------------
|
47
|
+
|
48
|
+
# Continuations
|
49
|
+
|
50
|
+
syntax('call-with-current-continuation') do |scope, callback|
|
51
|
+
continuation = Continuation.new(scope.runtime.stack)
|
52
|
+
callback = Heist.evaluate(callback, scope)
|
53
|
+
callback.call(scope, [continuation])
|
54
|
+
end
|
55
|
+
|
56
|
+
#----------------------------------------------------------------
|
57
|
+
|
58
|
+
# Quoting functions
|
59
|
+
|
60
|
+
# (quote) casts identifiers to symbols. If given a list, it
|
61
|
+
# quotes all items in the list recursively.
|
62
|
+
syntax('quote') do |scope, arg|
|
63
|
+
case arg
|
64
|
+
when List then
|
65
|
+
arg.inject(List.new) do |list, cell|
|
66
|
+
list << call('quote', scope, cell)
|
67
|
+
list
|
68
|
+
end
|
69
|
+
when Identifier then arg.to_s.to_sym
|
70
|
+
else arg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# (quasiquote) is similar to (quote), except that when it
|
75
|
+
# encounters an (unquote) or (unquote-splicing) expression
|
76
|
+
# it will evaluate it and insert the result into the
|
77
|
+
# surrounding quoted list.
|
78
|
+
syntax('quasiquote') do |scope, arg|
|
79
|
+
case arg
|
80
|
+
when List then
|
81
|
+
result = List.new
|
82
|
+
arg.each do |cell|
|
83
|
+
if List === cell
|
84
|
+
case cell.first.to_s
|
85
|
+
when 'unquote' then
|
86
|
+
result << Heist.evaluate(cell.last, scope)
|
87
|
+
when 'unquote-splicing' then
|
88
|
+
list = Heist.evaluate(cell.last, scope)
|
89
|
+
list.each { |value| result << value }
|
90
|
+
else
|
91
|
+
result << call('quasiquote', scope, cell)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
result << call('quasiquote', scope, cell)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
result
|
98
|
+
when Identifier then arg.to_s.to_sym
|
99
|
+
else arg
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
#----------------------------------------------------------------
|
104
|
+
|
105
|
+
# Control structures
|
106
|
+
|
107
|
+
# (begin) simply executes a series of lists in the current scope.
|
108
|
+
syntax('begin') do |scope, *body|
|
109
|
+
Body.new(body, scope)
|
110
|
+
end
|
111
|
+
|
112
|
+
# (if) evaluates the consequent if the condition eval's to
|
113
|
+
# true, otherwise it evaluates the alternative
|
114
|
+
syntax('if') do |scope, cond, cons, alt|
|
115
|
+
which = Heist.evaluate(cond, scope) ? cons : alt
|
116
|
+
Frame.new(which, scope)
|
117
|
+
end
|
118
|
+
|
119
|
+
#----------------------------------------------------------------
|
120
|
+
|
121
|
+
# Runtime utilities
|
122
|
+
|
123
|
+
define('exit') { exit }
|
124
|
+
|
125
|
+
syntax('runtime') do |scope|
|
126
|
+
scope.runtime.elapsed_time
|
127
|
+
end
|
128
|
+
|
129
|
+
syntax('eval') do |scope, string|
|
130
|
+
scope.eval(Heist.evaluate(string, scope))
|
131
|
+
end
|
132
|
+
|
133
|
+
define('display') do |expression|
|
134
|
+
print expression
|
135
|
+
end
|
136
|
+
|
137
|
+
syntax('load') do |scope, file|
|
138
|
+
scope.load(file)
|
139
|
+
end
|
140
|
+
|
141
|
+
define('error') do |message, *args|
|
142
|
+
raise RuntimeError.new("#{ message } :: #{ args * ', ' }")
|
143
|
+
end
|
144
|
+
|
145
|
+
#----------------------------------------------------------------
|
146
|
+
|
147
|
+
# Comparators
|
148
|
+
|
149
|
+
# TODO write a more exact implementation, and implement (eq?) and (equal?)
|
150
|
+
define('eqv?') do |op1, op2|
|
151
|
+
op1.class == op2.class and op1 == op2
|
152
|
+
end
|
153
|
+
|
154
|
+
# TODO raise an exception if they're not numeric
|
155
|
+
# Returns true iff all arguments are numerically equal
|
156
|
+
define('=') do |*args|
|
157
|
+
args.all? { |arg| arg == args.first }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns true iff the arguments are monotonically decreasing
|
161
|
+
define('>') do |*args|
|
162
|
+
result = true
|
163
|
+
args.inject { |former, latter| result = false unless former > latter }
|
164
|
+
result
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns true iff the arguments are monotonically non-increasing
|
168
|
+
define('>=') do |*args|
|
169
|
+
result = true
|
170
|
+
args.inject { |former, latter| result = false unless former >= latter }
|
171
|
+
result
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns true iff the arguments are monotonically increasing
|
175
|
+
define('<') do |*args|
|
176
|
+
result = true
|
177
|
+
args.inject { |former, latter| result = false unless former < latter }
|
178
|
+
result
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns true iff the arguments are monotonically non-decreasing
|
182
|
+
define('<=') do |*args|
|
183
|
+
result = true
|
184
|
+
args.inject { |former, latter| result = false unless former <= latter }
|
185
|
+
result
|
186
|
+
end
|
187
|
+
|
188
|
+
#----------------------------------------------------------------
|
189
|
+
|
190
|
+
# Type-checking predicates
|
191
|
+
|
192
|
+
define('complex?') do |value|
|
193
|
+
call('real?', value) # || TODO
|
194
|
+
end
|
195
|
+
|
196
|
+
define('real?') do |value|
|
197
|
+
call('rational?', value) || Float === value
|
198
|
+
end
|
199
|
+
|
200
|
+
define('rational?') do |value|
|
201
|
+
call('integer?', value) || Float === value # TODO handle this properly
|
202
|
+
end
|
203
|
+
|
204
|
+
define('integer?') do |value|
|
205
|
+
Integer === value
|
206
|
+
end
|
207
|
+
|
208
|
+
#----------------------------------------------------------------
|
209
|
+
|
210
|
+
# Numerical functions
|
211
|
+
# TODO implement exact?, inexact?, numerator, denominator, rationalize,
|
212
|
+
# complex functions, exact->inexact and vice versa
|
213
|
+
|
214
|
+
# TODO implement max/min in Scheme
|
215
|
+
|
216
|
+
# Returns the maximum value in the list of arguments
|
217
|
+
define('max') do |*args|
|
218
|
+
args.max
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns the minimum value in the list of arguments
|
222
|
+
define('min') do |*args|
|
223
|
+
args.min
|
224
|
+
end
|
225
|
+
|
226
|
+
# Returns the sum of all arguments passed
|
227
|
+
define('+') do |*args|
|
228
|
+
args.any? { |arg| String === arg } ?
|
229
|
+
args.inject("") { |str, arg| str + arg.to_s } :
|
230
|
+
args.inject(0) { |sum, arg| sum + arg }
|
231
|
+
end
|
232
|
+
|
233
|
+
# Subtracts the second argument from the first
|
234
|
+
define('-') do |op1, op2|
|
235
|
+
op2.nil? ? 0 - op1 : op1 - op2
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns the product of all arguments passed
|
239
|
+
define('*') do |*args|
|
240
|
+
args.inject(1) { |prod, arg| prod * arg }
|
241
|
+
end
|
242
|
+
|
243
|
+
# Returns the first argument divided by the second, or the
|
244
|
+
# reciprocal of the first if only one argument is given
|
245
|
+
define('/') do |op1, op2|
|
246
|
+
op2.nil? ? 1.0 / op1 : op1 / op2.to_f
|
247
|
+
end
|
248
|
+
|
249
|
+
# (quotient) and (remainder) satisfy
|
250
|
+
#
|
251
|
+
# (= n1 (+ (* n2 (quotient n1 n2))
|
252
|
+
# (remainder n1 n2)))
|
253
|
+
|
254
|
+
# Returns the quotient of two numbers, i.e. performs n1/n2
|
255
|
+
# and rounds toward zero.
|
256
|
+
define('quotient') do |op1, op2|
|
257
|
+
result = op1.to_i.to_f / op2.to_i
|
258
|
+
result > 0 ? result.floor : result.ceil
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns the remainder after dividing the first operand
|
262
|
+
# by the second
|
263
|
+
define('remainder') do |op1, op2|
|
264
|
+
op1.to_i - op2.to_i * call('quotient', op1, op2)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Returns the first operand modulo the second
|
268
|
+
define('modulo') do |op1, op2|
|
269
|
+
op1.to_i % op2.to_i
|
270
|
+
end
|
271
|
+
|
272
|
+
%w[floor ceil truncate round].each do |symbol|
|
273
|
+
define(symbol) do |number|
|
274
|
+
number.__send__(symbol)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
%w[exp log sin cos tan asin acos sqrt].each do |symbol|
|
279
|
+
define(symbol) do |number|
|
280
|
+
Math.__send__(symbol, number)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
define('atan') do |op1, op2|
|
285
|
+
op2.nil? ? Math.atan(op1) : Math.atan2(op1, op2)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Returns the result of raising the first argument to the
|
289
|
+
# power of the second
|
290
|
+
define('expt') do |op1, op2|
|
291
|
+
op1 ** op2
|
292
|
+
end
|
293
|
+
|
294
|
+
# Returns a random number in the range 0...max
|
295
|
+
define('random') do |max|
|
296
|
+
rand(max)
|
297
|
+
end
|
298
|
+
|
299
|
+
define('number->string') do |number, radix|
|
300
|
+
number.to_s(radix || 10)
|
301
|
+
end
|
302
|
+
|
303
|
+
define('string->number') do |string, radix|
|
304
|
+
radix.nil? ? string.to_f : string.to_i(radix)
|
305
|
+
end
|
306
|
+
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Control structures
|
2
|
+
|
3
|
+
# (cond) goes through a list of tests, evaluating each one
|
4
|
+
# in order of appearance. Once a matching precondition is
|
5
|
+
# found, its consequent is tail-called and no further
|
6
|
+
# preconditions are evaluated.
|
7
|
+
syntax('cond') do |scope, *pairs|
|
8
|
+
result = nil
|
9
|
+
pairs.each do |list|
|
10
|
+
next if result
|
11
|
+
test = list.first.to_s == 'else' || Heist.evaluate(list.first, scope)
|
12
|
+
next unless test
|
13
|
+
result = list[1].to_s == '=>' ?
|
14
|
+
Heist.evaluate(list[2], scope).call(scope, [test]) :
|
15
|
+
Body.new(list.rest, scope)
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
# (case) acts like Ruby's case statement. The value of the
|
21
|
+
# given expression is compared against a series of lists;
|
22
|
+
# once a list is found to include the value, the expressions
|
23
|
+
# following the list are evaluated and no further lists
|
24
|
+
# are tested.
|
25
|
+
syntax('case') do |scope, key, *clauses|
|
26
|
+
value = Heist.evaluate(key, scope)
|
27
|
+
result = nil
|
28
|
+
clauses.each do |list|
|
29
|
+
next if result
|
30
|
+
values = call('quote', scope, list.first)
|
31
|
+
result = Body.new(list.rest, scope) if values == :else or
|
32
|
+
values.include?(value)
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
#----------------------------------------------------------------
|
38
|
+
|
39
|
+
# Binding constructs
|
40
|
+
|
41
|
+
# (let), (let*) and (letrec) each create a new scope and bind
|
42
|
+
# values to some symbols before executing a series of lists.
|
43
|
+
# They differ according to how they evaluate the bound values.
|
44
|
+
|
45
|
+
# (let) evaluates values in the enclosing scope, so lambdas will
|
46
|
+
# not be able to refer to other values assigned using the (let).
|
47
|
+
syntax('let') do |scope, assignments, *body|
|
48
|
+
if Identifier === assignments
|
49
|
+
name = assignments
|
50
|
+
assignments = body.first
|
51
|
+
formals = assignments.map { |pair| pair.first }
|
52
|
+
values = assignments.map { |pair| pair.last }
|
53
|
+
closure = Scope.new(scope)
|
54
|
+
closure[name] = Function.new(closure, formals, body[1..-1])
|
55
|
+
closure[name].call(scope, values)
|
56
|
+
else
|
57
|
+
closure = Scope.new(scope)
|
58
|
+
assignments.each do |assign|
|
59
|
+
closure[assign.first] = Heist.evaluate(assign.last, scope)
|
60
|
+
end
|
61
|
+
call('begin', closure, *body)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# (let*) creates a new scope for each variable and evaluates
|
66
|
+
# each expression in its enclosing scope. Basically a shorthand
|
67
|
+
# for several nested (let)s. Variables may refer to those that
|
68
|
+
# preceed them but not vice versa.
|
69
|
+
syntax('let*') do |scope, assignments, *body|
|
70
|
+
closure = assignments.inject(scope) do |outer, assign|
|
71
|
+
inner = Scope.new(outer)
|
72
|
+
inner[assign.first] = Heist.evaluate(assign.last, outer)
|
73
|
+
inner
|
74
|
+
end
|
75
|
+
call('begin', closure, *body)
|
76
|
+
end
|
77
|
+
|
78
|
+
# (letrec) evaluates values in the inner scope, so lambdas are
|
79
|
+
# able to refer to other values assigned using the (letrec).
|
80
|
+
syntax('letrec') do |scope, assignments, *body|
|
81
|
+
closure = Scope.new(scope)
|
82
|
+
assignments.each do |assign|
|
83
|
+
closure[assign.first] = Heist.evaluate(assign.last, closure)
|
84
|
+
end
|
85
|
+
call('begin', closure, *body)
|
86
|
+
end
|
87
|
+
|
88
|
+
syntax('let-syntax') do |*args|
|
89
|
+
call('let', *args)
|
90
|
+
end
|
91
|
+
|
92
|
+
syntax('letrec-syntax') do |*args|
|
93
|
+
call('letrec', *args)
|
94
|
+
end
|
95
|
+
|
96
|
+
#----------------------------------------------------------------
|
97
|
+
|
98
|
+
# Iteration
|
99
|
+
|
100
|
+
# (do) is similar to the 'while' construct in procedural
|
101
|
+
# languages. It assigns initial values to a set of variables,
|
102
|
+
# then performs the list of given commands in a loop. If
|
103
|
+
# before any iteration the test is found to be false, the
|
104
|
+
# loop is halted and the value of the expression following
|
105
|
+
# the test is returned.
|
106
|
+
syntax('do') do |scope, assignments, test, *commands|
|
107
|
+
closure = Scope.new(scope)
|
108
|
+
assignments.each do |assign|
|
109
|
+
closure[assign.first] = Heist.evaluate(assign[1], scope)
|
110
|
+
end
|
111
|
+
while not Heist.evaluate(test.first, closure)
|
112
|
+
commands.each { |expr| Heist.evaluate(expr, closure) }
|
113
|
+
temp = {}
|
114
|
+
assignments.each do |assign|
|
115
|
+
step = assign[2] || assign[0]
|
116
|
+
temp[assign.first] = Heist.evaluate(step, closure)
|
117
|
+
end
|
118
|
+
assignments.each do |assign|
|
119
|
+
closure[assign.first] = temp[assign.first]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
call('begin', closure, *test.rest)
|
123
|
+
end
|
124
|
+
|
125
|
+
#----------------------------------------------------------------
|
126
|
+
|
127
|
+
# Boolean combinators
|
128
|
+
|
129
|
+
# (and) returns the first falsey value returned by the list
|
130
|
+
# of expressions, or returns the value of the last expression
|
131
|
+
# if all values are truthy.
|
132
|
+
syntax('and') do |scope, *args|
|
133
|
+
result = true
|
134
|
+
args.each do |arg|
|
135
|
+
next if not result
|
136
|
+
result = Heist.evaluate(arg, scope)
|
137
|
+
end
|
138
|
+
result
|
139
|
+
end
|
140
|
+
|
141
|
+
# (or) returns the first truthy value returned by the list
|
142
|
+
# of expressions, or returns the value of the last expression
|
143
|
+
# if all values are falsey.
|
144
|
+
syntax('or') do |scope, *args|
|
145
|
+
result = false
|
146
|
+
args.each do |arg|
|
147
|
+
next if result
|
148
|
+
result = Heist.evaluate(arg, scope)
|
149
|
+
end
|
150
|
+
result
|
151
|
+
end
|
152
|
+
|
153
|
+
#----------------------------------------------------------------
|
154
|
+
|
155
|
+
# Delayed evaluation
|
156
|
+
|
157
|
+
# (delay) allows the evaluation of an expression to be delayed
|
158
|
+
# by wrapping it in a promise. Use (force) to evaluate the promise
|
159
|
+
# at a later time. The expression inside a promise is only
|
160
|
+
# ever evaluated once, so a promise can be implemented as a
|
161
|
+
# memoized closure.
|
162
|
+
syntax('delay') do |scope, expression|
|
163
|
+
promise = Binding.new(expression, scope)
|
164
|
+
Function.new(scope) { promise.extract }
|
165
|
+
end
|
166
|
+
|