heist 0.1.0 → 0.2.0

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