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.
Files changed (53) hide show
  1. data/History.txt +21 -0
  2. data/Manifest.txt +53 -0
  3. data/README.txt +274 -0
  4. data/Rakefile +12 -0
  5. data/bin/heist +16 -0
  6. data/lib/bin_spec.rb +25 -0
  7. data/lib/builtin/library.scm +95 -0
  8. data/lib/builtin/primitives.rb +306 -0
  9. data/lib/builtin/syntax.rb +166 -0
  10. data/lib/builtin/syntax.scm +155 -0
  11. data/lib/heist.rb +47 -0
  12. data/lib/parser/nodes.rb +105 -0
  13. data/lib/parser/scheme.rb +1081 -0
  14. data/lib/parser/scheme.tt +80 -0
  15. data/lib/repl.rb +112 -0
  16. data/lib/runtime/binding.rb +31 -0
  17. data/lib/runtime/callable/continuation.rb +24 -0
  18. data/lib/runtime/callable/function.rb +55 -0
  19. data/lib/runtime/callable/macro.rb +170 -0
  20. data/lib/runtime/callable/macro/expansion.rb +15 -0
  21. data/lib/runtime/callable/macro/matches.rb +77 -0
  22. data/lib/runtime/callable/macro/splice.rb +56 -0
  23. data/lib/runtime/data/expression.rb +23 -0
  24. data/lib/runtime/data/identifier.rb +20 -0
  25. data/lib/runtime/data/list.rb +36 -0
  26. data/lib/runtime/frame.rb +118 -0
  27. data/lib/runtime/runtime.rb +61 -0
  28. data/lib/runtime/scope.rb +121 -0
  29. data/lib/runtime/stack.rb +60 -0
  30. data/lib/runtime/stackless.rb +49 -0
  31. data/lib/stdlib/benchmark.scm +12 -0
  32. data/lib/stdlib/birdhouse.scm +82 -0
  33. data/test/arithmetic.scm +57 -0
  34. data/test/benchmarks.scm +27 -0
  35. data/test/booleans.scm +6 -0
  36. data/test/closures.scm +16 -0
  37. data/test/conditionals.scm +55 -0
  38. data/test/continuations.scm +144 -0
  39. data/test/define_functions.scm +27 -0
  40. data/test/define_values.scm +28 -0
  41. data/test/delay.scm +8 -0
  42. data/test/file_loading.scm +9 -0
  43. data/test/hygienic.scm +39 -0
  44. data/test/let.scm +42 -0
  45. data/test/lib.scm +2 -0
  46. data/test/macro-helpers.scm +19 -0
  47. data/test/macros.scm +343 -0
  48. data/test/numbers.scm +19 -0
  49. data/test/plt-macros.txt +40 -0
  50. data/test/test_heist.rb +84 -0
  51. data/test/unhygienic.scm +11 -0
  52. data/test/vars.scm +2 -0
  53. 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
+