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