heist 0.1.0 → 0.2.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.
@@ -1,34 +1,35 @@
1
1
  # Functions that create new functions
2
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)
3
+ # (define) binds values to names in the current scope. If the
4
+ # first parameter is a list it creates a function, otherwise
5
+ # it eval's the second parameter and binds it to the name
6
+ # given by the first.
7
+ syntax('define') do |scope, cells|
8
+ name = cells.car
9
+ Cons === name ?
10
+ scope.define(name.car, name.cdr, cells.cdr) :
11
+ scope[name] = Heist.evaluate(cells.cdr.car, scope)
11
12
  end
12
13
 
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)
14
+ # (lambda) returns an anonymous function whose arguments are
15
+ # named by the first parameter and whose body is given by the
16
+ # remaining parameters.
17
+ syntax('lambda') do |scope, cells|
18
+ Function.new(scope, cells.car, cells.cdr)
18
19
  end
19
20
 
20
21
  # (set!) reassigns the value of an existing bound variable,
21
22
  # in the innermost scope responsible for binding it.
22
- syntax('set!') do |scope, name, value|
23
- scope.set!(name, Heist.evaluate(value, scope))
23
+ syntax('set!') do |scope, cells|
24
+ scope.set!(cells.car, Heist.evaluate(cells.cdr.car, scope))
24
25
  end
25
26
 
26
27
  #----------------------------------------------------------------
27
28
 
28
29
  # Macros
29
30
 
30
- syntax('define-syntax') do |scope, name, transformer|
31
- scope[name] = Heist.evaluate(transformer, scope)
31
+ syntax('define-syntax') do |scope, cells|
32
+ scope[cells.car] = Heist.evaluate(cells.cdr.car, scope)
32
33
  end
33
34
 
34
35
  syntax('let-syntax') do |*args|
@@ -39,65 +40,34 @@ syntax('letrec-syntax') do |*args|
39
40
  call('letrec', *args)
40
41
  end
41
42
 
42
- syntax('syntax-rules') do |scope, keywords, *rules|
43
- Macro.new(scope, keywords, rules)
43
+ syntax('syntax-rules') do |scope, cells|
44
+ Macro.new(scope, cells.car, cells.cdr)
44
45
  end
45
46
 
46
47
  #----------------------------------------------------------------
47
48
 
48
49
  # Continuations
49
50
 
50
- syntax('call-with-current-continuation') do |scope, callback|
51
+ # Creates a +Continuation+ encapsulating the current +Stack+
52
+ # state, and returns the result of calling the second parameter
53
+ # (which should evaluate to a +Function+) with the continuation
54
+ # as the argument.
55
+ syntax('call-with-current-continuation') do |scope, cells|
51
56
  continuation = Continuation.new(scope.runtime.stack)
52
- callback = Heist.evaluate(callback, scope)
53
- callback.call(scope, [continuation])
57
+ callback = Heist.evaluate(cells.car, scope)
58
+ callback.call(scope, Cons.new(continuation))
54
59
  end
55
60
 
56
61
  #----------------------------------------------------------------
57
62
 
58
63
  # Quoting functions
59
64
 
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
65
+ # (quote) treats its argument as a literal. Returns the given
66
+ # portion of the parse tree as a list
67
+ syntax('quote') do |scope, cells|
68
+ node = cells.car
69
+ node.freeze! if node.respond_to?(:freeze!)
70
+ node
101
71
  end
102
72
 
103
73
  #----------------------------------------------------------------
@@ -105,39 +75,52 @@ end
105
75
  # Control structures
106
76
 
107
77
  # (begin) simply executes a series of lists in the current scope.
108
- syntax('begin') do |scope, *body|
109
- Body.new(body, scope)
78
+ syntax('begin') do |scope, cells|
79
+ Body.new(cells, scope)
110
80
  end
111
81
 
112
82
  # (if) evaluates the consequent if the condition eval's to
113
83
  # 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)
84
+ syntax('if') do |scope, cells|
85
+ which = Heist.evaluate(cells.car, scope) ? cells.cdr : cells.cdr.cdr
86
+ which.null? ? which : Frame.new(which.car, scope)
117
87
  end
118
88
 
119
89
  #----------------------------------------------------------------
120
90
 
121
91
  # Runtime utilities
122
92
 
93
+ # (exit) causes the host Ruby process to quit
123
94
  define('exit') { exit }
124
95
 
125
- syntax('runtime') do |scope|
96
+ # (runtime) returns the amount of time the host +Runtime+ has
97
+ # been alive, in microseconds. Not a standard function, but
98
+ # used in SICP.
99
+ syntax('runtime') do |scope, cells|
126
100
  scope.runtime.elapsed_time
127
101
  end
128
102
 
129
- syntax('eval') do |scope, string|
130
- scope.eval(Heist.evaluate(string, scope))
103
+ # (eval) evaluates Scheme code and returns the result. The
104
+ # argument can be a string or a list containing a valid
105
+ # Scheme expression.
106
+ syntax('eval') do |scope, cells|
107
+ scope.eval(Heist.evaluate(cells.car, scope))
131
108
  end
132
109
 
110
+ # (display) prints the given value to the console
133
111
  define('display') do |expression|
134
112
  print expression
135
113
  end
136
114
 
137
- syntax('load') do |scope, file|
138
- scope.load(file)
115
+ # (load) loads a file containing Scheme code and executes its
116
+ # contents. The path can be relative to the current file, or
117
+ # it can be the name of a file in the Heist library.
118
+ syntax('load') do |scope, cells|
119
+ scope.load(cells.car)
139
120
  end
140
121
 
122
+ # (error) raises an error with the given message. Additional
123
+ # arguments are appended to the message.
141
124
  define('error') do |message, *args|
142
125
  raise RuntimeError.new("#{ message } :: #{ args * ', ' }")
143
126
  end
@@ -146,9 +129,21 @@ end
146
129
 
147
130
  # Comparators
148
131
 
149
- # TODO write a more exact implementation, and implement (eq?) and (equal?)
132
+ # TODO write a more exact implementation, and implement (eq?)
150
133
  define('eqv?') do |op1, op2|
151
- op1.class == op2.class and op1 == op2
134
+ (Identifier === op1 and op1 == op2) or op1.equal?(op2)
135
+ end
136
+
137
+ define('equal?') do |op1, op2|
138
+ op1 == op2
139
+ end
140
+
141
+ # Returns true iff the given number is exact i.e. an integer, a
142
+ # rational, or a complex made of integers
143
+ define('exact?') do |value|
144
+ call('rational?', value) || (Complex === value &&
145
+ call('rational?', value.real) &&
146
+ call('rational?', value.imag))
152
147
  end
153
148
 
154
149
  # TODO raise an exception if they're not numeric
@@ -190,39 +185,43 @@ end
190
185
  # Type-checking predicates
191
186
 
192
187
  define('complex?') do |value|
193
- call('real?', value) # || TODO
188
+ Complex === value || call('real?', value)
194
189
  end
195
190
 
196
191
  define('real?') do |value|
197
- call('rational?', value) || Float === value
192
+ Float === value || call('rational?', value)
198
193
  end
199
194
 
200
195
  define('rational?') do |value|
201
- call('integer?', value) || Float === value # TODO handle this properly
196
+ Rational === value || call('integer?', value)
202
197
  end
203
198
 
204
199
  define('integer?') do |value|
205
200
  Integer === value
206
201
  end
207
202
 
208
- #----------------------------------------------------------------
209
-
210
- # Numerical functions
211
- # TODO implement exact?, inexact?, numerator, denominator, rationalize,
212
- # complex functions, exact->inexact and vice versa
203
+ define('string?') do |value|
204
+ String === value
205
+ end
213
206
 
214
- # TODO implement max/min in Scheme
207
+ define('symbol?') do |value|
208
+ Symbol === value or Identifier === value
209
+ end
215
210
 
216
- # Returns the maximum value in the list of arguments
217
- define('max') do |*args|
218
- args.max
211
+ define('procedure?') do |value|
212
+ Function === value
219
213
  end
220
214
 
221
- # Returns the minimum value in the list of arguments
222
- define('min') do |*args|
223
- args.min
215
+ define('pair?') do |value|
216
+ Cons === value and value.pair?
224
217
  end
225
218
 
219
+ #----------------------------------------------------------------
220
+
221
+ # Numerical functions
222
+ # TODO implement exact?, inexact?, numerator, denominator, rationalize,
223
+ # complex functions, exact->inexact and vice versa
224
+
226
225
  # Returns the sum of all arguments passed
227
226
  define('+') do |*args|
228
227
  args.any? { |arg| String === arg } ?
@@ -243,7 +242,7 @@ end
243
242
  # Returns the first argument divided by the second, or the
244
243
  # reciprocal of the first if only one argument is given
245
244
  define('/') do |op1, op2|
246
- op2.nil? ? 1.0 / op1 : op1 / op2.to_f
245
+ op2.nil? ? Heist.divide(1, op1) : Heist.divide(op1, op2)
247
246
  end
248
247
 
249
248
  # (quotient) and (remainder) satisfy
@@ -269,6 +268,16 @@ define('modulo') do |op1, op2|
269
268
  op1.to_i % op2.to_i
270
269
  end
271
270
 
271
+ # Returns the numerator of a number
272
+ define('numerator') do |value|
273
+ Rational === value ? value.numerator : value
274
+ end
275
+
276
+ # Returns the denominator of a number
277
+ define('denominator') do |value|
278
+ Rational === value ? value.denominator : 1
279
+ end
280
+
272
281
  %w[floor ceil truncate round].each do |symbol|
273
282
  define(symbol) do |number|
274
283
  number.__send__(symbol)
@@ -291,16 +300,69 @@ define('expt') do |op1, op2|
291
300
  op1 ** op2
292
301
  end
293
302
 
303
+ # Returns a new complex number with the given real and
304
+ # imaginary parts
305
+ define('make-rectangular') do |real, imag|
306
+ Complex.new(real, imag)
307
+ end
308
+
309
+ # Returns the real part of a number
310
+ define('real-part') do |value|
311
+ Complex === value ? value.real : value
312
+ end
313
+
314
+ # Returns the imaginary part of a number, which is zero
315
+ # unless the number is not real
316
+ define('imag-part') do |value|
317
+ Complex === value ? value.imag : 0
318
+ end
319
+
294
320
  # Returns a random number in the range 0...max
295
321
  define('random') do |max|
296
322
  rand(max)
297
323
  end
298
324
 
325
+ # Casts a number to a string
299
326
  define('number->string') do |number, radix|
300
327
  number.to_s(radix || 10)
301
- end
302
328
 
329
+ end
330
+ # Casts a string to a number
303
331
  define('string->number') do |string, radix|
304
332
  radix.nil? ? string.to_f : string.to_i(radix)
305
333
  end
306
334
 
335
+ #----------------------------------------------------------------
336
+
337
+ # List/pair functions
338
+
339
+ # Allocates and returns a new pair from its arguments
340
+ define('cons') do |car, cdr|
341
+ Cons.new(car, cdr)
342
+ end
343
+
344
+ # car/cdr accessors (dynamically generated)
345
+ Cons::ACCESSORS.each do |accsr|
346
+ define(accsr) do |cons|
347
+ cons.__send__(accsr)
348
+ end
349
+ end
350
+
351
+ # Mutators for car/cdr fields
352
+ define('set-car!') do |cons, value|
353
+ cons.car = value
354
+ end
355
+ define('set-cdr!') do |cons, value|
356
+ cons.cdr = value
357
+ end
358
+
359
+ #----------------------------------------------------------------
360
+
361
+ # Control features
362
+
363
+ # Calls a function using a list for the arguments
364
+ # TODO take multiple argument values instead of a single list
365
+ define('apply') do |function, list|
366
+ function.apply(list.to_a)
367
+ end
368
+
@@ -27,16 +27,13 @@
27
27
  [(case key) #f]
28
28
  [(case key (else expr1 expr2 ...))
29
29
  (begin expr1 expr2 ...)]
30
- [(case key (() expr ...) clause ...)
31
- (case key clause ...)]
32
30
  [(case key
33
- ((datum1 datum2 ...) expr1 expr2 ...)
31
+ ((cell ...) expr1 expr2 ...)
34
32
  clause ...)
35
33
  (let ([temp key])
36
- (if (eqv? temp 'datum1)
34
+ (if (member temp '(cell ...))
37
35
  (begin expr1 expr2 ...)
38
36
  (case temp
39
- ((datum2 ...) expr1 expr2 ...)
40
37
  clause ...)))]))
41
38
 
42
39
  ;----------------------------------------------------------------
@@ -153,3 +150,23 @@
153
150
  (set! forced #t)
154
151
  memo))))]))
155
152
 
153
+ ;----------------------------------------------------------------
154
+
155
+ ; Quasiquotation
156
+
157
+ ; (quasiquote) is similar to (quote), except that when it
158
+ ; encounters an (unquote) or (unquote-splicing) expression
159
+ ; it will evaluate it and insert the result into the
160
+ ; surrounding quoted list.
161
+ (define-syntax quasiquote (syntax-rules (unquote unquote-splicing)
162
+ [(quasiquote (unquote expr))
163
+ expr]
164
+ [(quasiquote ((unquote-splicing expr)))
165
+ expr]
166
+ [(quasiquote ((unquote-splicing expr) . rest))
167
+ (append expr (quasiquote rest))]
168
+ [(quasiquote (first . rest))
169
+ (cons (quasiquote first) (quasiquote rest))]
170
+ [(quasiquote expr)
171
+ 'expr]))
172
+
data/lib/heist.rb CHANGED
@@ -1,8 +1,15 @@
1
+ require 'forwardable'
2
+ require 'rational'
3
+ require 'complex'
4
+
1
5
  require 'rubygems'
2
6
  require 'treetop'
3
7
 
8
+ # +Heist+ is the root module for all of Heist's components, and hosts a few
9
+ # utility methods that don't belong anywhere else. See the README for an
10
+ # overview of Heist's features.
4
11
  module Heist
5
- VERSION = '0.1.0'
12
+ VERSION = '0.2.0'
6
13
 
7
14
  ROOT_PATH = File.expand_path(File.dirname(__FILE__))
8
15
  PARSER_PATH = ROOT_PATH + '/parser/'
@@ -10,6 +17,7 @@ module Heist
10
17
  BUILTIN_PATH = ROOT_PATH + '/builtin/'
11
18
  LIB_PATH = ROOT_PATH + '/stdlib/'
12
19
 
20
+ require PARSER_PATH + 'ruby'
13
21
  require PARSER_PATH + 'scheme'
14
22
  require PARSER_PATH + 'nodes'
15
23
  require RUNTIME_PATH + 'runtime'
@@ -24,23 +32,47 @@ module Heist
24
32
  class SyntaxError < RuntimeError; end
25
33
  class MacroError < SyntaxError; end
26
34
  class MacroTemplateMismatch < MacroError; end
35
+ class TypeError < RuntimeError; end
36
+ class ImmutableError < TypeError; end
27
37
 
28
- def self.parse(source)
29
- @parser ||= SchemeParser.new
30
- @parser.parse(source)
31
- end
32
-
33
- def self.evaluate(expression, scope)
34
- Runtime::Expression === expression ?
35
- expression.eval(scope) :
36
- expression
37
- end
38
-
39
- def self.info(runtime)
40
- puts "Heist Scheme interpreter v. #{ VERSION }"
41
- puts "Evaluation mode: #{ runtime.lazy? ? 'LAZY' : 'EAGER' }"
42
- puts "Continuations enabled? #{ runtime.stackless? ? 'NO' : 'YES' }"
43
- puts "Macros: #{ runtime.hygienic? ? 'HYGIENIC' : 'UNHYGIENIC' }\n\n"
38
+ class << self
39
+ # Accepts either a string of Scheme code or an array of Ruby data and parses
40
+ # into a +Cons+ list structure. Scheme code is converted to a +Program+,
41
+ # while a Ruby array is converted to a single list expression. Returns +nil+
42
+ # if the input cannot be parsed successfully.
43
+ def parse(source)
44
+ @scheme ||= SchemeParser.new
45
+ @ruby ||= RubyParser.new
46
+ parser = (String === source) ? @scheme : @ruby
47
+ parser.parse(source)
48
+ end
49
+
50
+ # Returns the result of evaluating the given +Expression+ in the given +Scope+.
51
+ # If the first argument is not an +Expression+ it will be returned unaltered.
52
+ def evaluate(expression, scope)
53
+ Runtime::Expression === expression ?
54
+ expression.eval(scope) :
55
+ expression
56
+ end
57
+
58
+ # Returns a new complex number with the given +real+ and +imaginary+ parts.
59
+ def complex(real, imaginary)
60
+ Complex.new(real, imaginary)
61
+ end
62
+
63
+ # Returns a new rational number with the given +numerator+ and +denominator+.
64
+ def rational(numerator, denominator)
65
+ Rational(numerator, denominator)
66
+ end
67
+
68
+ # Returns the result of dividing the first argument by the second. If both
69
+ # arguments are integers, returns a rational rather than performing
70
+ # integer division as Ruby would normally do.
71
+ def divide(op1, op2)
72
+ [op1, op2].all? { |value| Integer === value } ?
73
+ rational(op1, op2) :
74
+ op1.to_f / op2
75
+ end
44
76
  end
45
77
 
46
78
  end