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.
- data/History.txt +17 -0
- data/Manifest.txt +23 -19
- data/README.txt +84 -52
- data/lib/builtin/library.scm +208 -10
- data/lib/builtin/primitives.rb +154 -92
- data/lib/builtin/syntax.scm +22 -5
- data/lib/heist.rb +49 -17
- data/lib/parser/nodes.rb +47 -24
- data/lib/parser/ruby.rb +29 -0
- data/lib/parser/scheme.rb +455 -143
- data/lib/parser/scheme.tt +23 -5
- data/lib/repl.rb +19 -16
- data/lib/runtime/binding.rb +24 -2
- data/lib/runtime/callable/continuation.rb +23 -2
- data/lib/runtime/callable/function.rb +122 -21
- data/lib/runtime/callable/macro.rb +169 -123
- data/lib/runtime/callable/macro/expansion.rb +137 -2
- data/lib/runtime/callable/macro/matches.rb +125 -41
- data/lib/runtime/callable/macro/tree.rb +141 -0
- data/lib/runtime/callable/syntax.rb +44 -0
- data/lib/runtime/data/cons.rb +234 -0
- data/lib/runtime/data/expression.rb +15 -6
- data/lib/runtime/data/identifier.rb +19 -2
- data/lib/runtime/frame.rb +102 -35
- data/lib/runtime/runtime.rb +44 -19
- data/lib/runtime/scope.rb +145 -30
- data/lib/runtime/stack.rb +103 -1
- data/lib/runtime/stackless.rb +48 -6
- data/test/arithmetic.scm +11 -2
- data/test/continuations.scm +16 -2
- data/test/equivalence.scm +34 -0
- data/test/functional.scm +4 -0
- data/test/lists.scm +78 -0
- data/test/macro-helpers.scm +1 -0
- data/test/macros.scm +111 -24
- data/test/numbers.scm +30 -8
- data/test/test_heist.rb +67 -12
- metadata +25 -21
- data/lib/builtin/syntax.rb +0 -166
- data/lib/runtime/callable/macro/splice.rb +0 -56
- data/lib/runtime/data/list.rb +0 -36
data/lib/builtin/primitives.rb
CHANGED
@@ -1,34 +1,35 @@
|
|
1
1
|
# Functions that create new functions
|
2
2
|
|
3
|
-
# (define) binds values to names in the current scope.
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
syntax('define') do |scope,
|
8
|
-
|
9
|
-
|
10
|
-
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
|
-
#
|
15
|
-
#
|
16
|
-
syntax('lambda') do |scope,
|
17
|
-
Function.new(scope,
|
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,
|
23
|
-
scope.set!(
|
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,
|
31
|
-
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,
|
43
|
-
Macro.new(scope,
|
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
|
-
|
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(
|
53
|
-
callback.call(scope,
|
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)
|
61
|
-
#
|
62
|
-
syntax('quote') do |scope,
|
63
|
-
|
64
|
-
|
65
|
-
|
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,
|
109
|
-
Body.new(
|
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,
|
115
|
-
which = Heist.evaluate(
|
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
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
138
|
-
|
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?)
|
132
|
+
# TODO write a more exact implementation, and implement (eq?)
|
150
133
|
define('eqv?') do |op1, op2|
|
151
|
-
|
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)
|
188
|
+
Complex === value || call('real?', value)
|
194
189
|
end
|
195
190
|
|
196
191
|
define('real?') do |value|
|
197
|
-
call('rational?', value)
|
192
|
+
Float === value || call('rational?', value)
|
198
193
|
end
|
199
194
|
|
200
195
|
define('rational?') do |value|
|
201
|
-
call('integer?', value)
|
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
|
-
|
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
|
-
|
207
|
+
define('symbol?') do |value|
|
208
|
+
Symbol === value or Identifier === value
|
209
|
+
end
|
215
210
|
|
216
|
-
|
217
|
-
|
218
|
-
args.max
|
211
|
+
define('procedure?') do |value|
|
212
|
+
Function === value
|
219
213
|
end
|
220
214
|
|
221
|
-
|
222
|
-
|
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
|
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
|
+
|
data/lib/builtin/syntax.scm
CHANGED
@@ -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
|
-
((
|
31
|
+
((cell ...) expr1 expr2 ...)
|
34
32
|
clause ...)
|
35
33
|
(let ([temp key])
|
36
|
-
(if (
|
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.
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|