heist 0.1.0

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