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,21 @@
1
+ === Version 0.1.0 (2009-02-25)
2
+
3
+ First public release
4
+ * Data: booleans, numbers, strings, symbols, proper lists
5
+ * Most of R5RS syntax (chapter 4)
6
+ * Quoting, quasiquoting
7
+ * Binding constructs, conditionals
8
+ * Boolean logic
9
+ * Delayed evaluation
10
+ * Macros
11
+ * Tail recursion
12
+ * Continuations
13
+ * Optional transparent lazy evaluation
14
+ * Decent chunk of R5RS numeric library
15
+
16
+
17
+ === Version 0 (2009-01-12)
18
+
19
+ Initial draft. Supports (define), (lambda), lexical scoping,
20
+ closures, arithmetic and (display).
21
+
@@ -0,0 +1,53 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/heist
6
+ lib/heist.rb
7
+ lib/bin_spec.rb
8
+ lib/repl.rb
9
+ lib/builtin/primitives.rb
10
+ lib/builtin/syntax.rb
11
+ lib/builtin/syntax.scm
12
+ lib/builtin/library.scm
13
+ lib/parser/scheme.tt
14
+ lib/parser/scheme.rb
15
+ lib/parser/nodes.rb
16
+ lib/runtime/runtime.rb
17
+ lib/runtime/data/expression.rb
18
+ lib/runtime/data/identifier.rb
19
+ lib/runtime/data/list.rb
20
+ lib/runtime/callable/function.rb
21
+ lib/runtime/callable/macro.rb
22
+ lib/runtime/callable/macro/matches.rb
23
+ lib/runtime/callable/macro/splice.rb
24
+ lib/runtime/callable/macro/expansion.rb
25
+ lib/runtime/callable/continuation.rb
26
+ lib/runtime/stack.rb
27
+ lib/runtime/stackless.rb
28
+ lib/runtime/frame.rb
29
+ lib/runtime/scope.rb
30
+ lib/runtime/binding.rb
31
+ lib/stdlib/benchmark.scm
32
+ lib/stdlib/birdhouse.scm
33
+ test/test_heist.rb
34
+ test/arithmetic.scm
35
+ test/benchmarks.scm
36
+ test/booleans.scm
37
+ test/closures.scm
38
+ test/conditionals.scm
39
+ test/continuations.scm
40
+ test/define_functions.scm
41
+ test/define_values.scm
42
+ test/delay.scm
43
+ test/file_loading.scm
44
+ test/let.scm
45
+ test/lib.scm
46
+ test/macro-helpers.scm
47
+ test/macros.scm
48
+ test/hygienic.scm
49
+ test/unhygienic.scm
50
+ test/plt-macros.txt
51
+ test/numbers.scm
52
+ test/vars.scm
53
+
@@ -0,0 +1,274 @@
1
+ = Heist
2
+
3
+ Heist is a Scheme interpreter written in Ruby. It provides an executable
4
+ for running Scheme code directly, and also allows the interpreter to be
5
+ easily embedded in, and extended using, Ruby applications. It nominally
6
+ targets R5RS, though is likely to be more liberal than other implementations.
7
+
8
+
9
+ == What is Scheme?
10
+
11
+ Scheme is a dialect of Lisp, one of the oldest programming languages still
12
+ in use today. Lisp was the first language to implement conditionals, first
13
+ class functions (lambdas), closures and recursion. Some Lisp features, such
14
+ as closures and macros, have yet to penetrate mainstream languages and it
15
+ has been observed that mainstream languages are slowly converging on the
16
+ feature set of Lisp.
17
+
18
+ Scheme in particular is important for its influence on current languages,
19
+ in particular JavaScript and Ruby (JavaScript is remarkably similar to
20
+ Scheme if you ignore the syntactic differences). It is also used in the
21
+ much-praised book "Structure and Interpretation of Computer Programs",
22
+ wherein it is used to explore some of the theory surrounding interpreters.
23
+
24
+ It is a functional language, in that it supports first-class functions
25
+ and many constructs that would be syntax in other languages resemble
26
+ function calls in Scheme. However it is not purely functional as it
27
+ allows side effects, and is more procedural in nature than, say, a
28
+ declarative language such as Haskell. Like other Lisps, its lack of
29
+ predefined syntax and its support for macros (functions that rewrite
30
+ source code) make it ideal for creating new syntactic forms and embedded
31
+ languages.
32
+
33
+
34
+ == Features
35
+
36
+ Heist nominally targets R5RS and is still in the early stages of development
37
+ (at time of writing it is about a month old). The priority at this stage
38
+ is runtime support for features that cannot be directly expressed as
39
+ Scheme functions. The library is therefore rather small but the runtime
40
+ is more advanced than some other toy Schemes I've seen.
41
+
42
+ Currently implemented R5RS features include:
43
+
44
+ * Boolean literals: #t and #f
45
+ * Numeric literals for integers, reals and rationals (though rationals
46
+ are currently converted to floating point)
47
+ * String and symbol literals
48
+ * Proper lists and full (quasi)quoting with <tt>'</tt>, <tt>`</tt>,
49
+ <tt>,</tt>, <tt>,@</tt>
50
+ * Macros: <tt>(syntax-rules)</tt>, <tt>(define-syntax)</tt>,
51
+ <tt>(let-syntax)</tt>, <tt>(letrec-syntax)</tt>. Supports keywords,
52
+ lists, pattern variables and ellipses down to arbitrary depth
53
+ * Continuations: <tt>(call-with-current-continuation)</tt>, aliased
54
+ as <tt>(call/cc)</tt>
55
+ * Proper tail recursion
56
+ * Variable assignment: <tt>(define)</tt> and <tt>(set!)</tt>
57
+ * Lambda expressions: <tt>(lambda (x) body)</tt>, including lexical
58
+ scoping and closures (varargs are currently not supported)
59
+ * Control flow: <tt>(begin)</tt>, <tt>(if)</tt>, <tt>(cond)</tt>,
60
+ <tt>(case)</tt>
61
+ * Binding constructs: <tt>(let)</tt>, <tt>(let*)</tt>, <tt>(letrec)</tt>
62
+ * Iteration: named <tt>(let)</tt>, <tt>(do)</tt>
63
+ * Delayed evaluation: <tt>(delay)</tt> and <tt>(force)</tt>
64
+ * Logical connectives: <tt>(and)</tt>, <tt>(or)</tt>, <tt>(not)</tt>
65
+ * Type checking: <tt>(boolean?)</tt>, <tt>(number?)</tt>, <tt>(complex?)</tt>,
66
+ <tt>(real?)</tt>, <tt>(rational?)</tt>, <tt>(integer?)</tt>
67
+ * Comparators: <tt>(eqv?)</tt>, <tt>(=)</tt>, <tt>(<)</tt>, <tt>(<=)</tt>,
68
+ <tt>(>)</tt>, <tt>(>=)</tt>
69
+ * Numeric library: <tt>(max)</tt>, <tt>(min)</tt>, <tt>(+)</tt>,
70
+ <tt>(-)</tt>, <tt>(*)</tt>, <tt>(/)</tt>, <tt>(quotient)</tt>,
71
+ <tt>(remainder)</tt>, <tt>(modulo)</tt>, <tt>(floor)</tt>,
72
+ <tt>(ceiling)</tt>, <tt>(truncate)</tt>, <tt>(round)</tt>,
73
+ <tt>(exp)</tt>, <tt>(log)</tt>, <tt>(sin)</tt>, <tt>(cos)</tt>,
74
+ <tt>(tan)</tt>, <tt>(asin)</tt>, <tt>(acos)</tt>, <tt>(atan)</tt>,
75
+ <tt>(expt)</tt>, <tt>(sqrt)</tt>, <tt>(zero?)</tt>, <tt>(positive?)</tt>,
76
+ <tt>(negative?)</tt>, <tt>(odd?)</tt>, <tt>(even?)</tt>, <tt>(abs)</tt>,
77
+ <tt>(gcd)</tt>, <tt>(lcm)</tt>, <tt>(factorial)</tt>,
78
+ <tt>(number->string)</tt>
79
+ * String library: <tt>(display)</tt>, <tt>(newline)</tt>,
80
+ <tt>(string->number)</tt>
81
+ * File loading: <tt>(load "foo.scm")</tt>, paths are relative to the
82
+ current file
83
+
84
+ In addition to the above R5RS features, the following are provided. Heist
85
+ allows the behaviour of some of these features to be configured on a
86
+ per-runtime-environment basis.
87
+
88
+ * <b>More-helpful-than-usual REPL</b>. Heist's REPL does not clutter
89
+ your display with prompts and other such cruft. It indents your
90
+ code automatically, supports tab completion, and output is printed
91
+ as comments so it's simple to copy code out of a REPL session into
92
+ a file without modification.
93
+ * <b>Transparent lazy evaluation (call by need)</b>. Expressions are not
94
+ evaluated before being passed as arguments, instead they are
95
+ evaluated as and when the called function requires their values.
96
+ * <b>Unhygienic macros</b>. Scheme mandates hygienic macros that
97
+ preserve lexical scoping, but other Lisps use a simpler system that
98
+ does not impose this restriction. Heist allows hygiene to be
99
+ disabled, its author not having the requisite experience to decide
100
+ which system he prefers yet.
101
+ * <b>Optional continuations</b>. Supporting continuations incurs a
102
+ constant performance overhead, so if you don't need them you can
103
+ switch them off.
104
+
105
+
106
+ == Installation and Usage
107
+
108
+ sudo gem install hoe
109
+ sudo gem install heist
110
+
111
+ Heist can be run from the command line or embedded in Ruby applications. To
112
+ run a Scheme file:
113
+
114
+ heist path/to/file.scm
115
+
116
+ To start an REPL session:
117
+
118
+ heist -i
119
+
120
+ Both these commands accept a number of runtime configuration options;
121
+ run <tt>heist -h</tt> for more information.
122
+
123
+ To embed Heist in a Ruby application, you need to create an instance of
124
+ the runtime:
125
+
126
+ require 'rubygems'
127
+ require 'heist'
128
+ scheme = Heist::Runtime.new(options)
129
+
130
+ <tt>options</tt> is an optional argument and is a hash that specifies
131
+ the behaviour of optional parts of the runtime. For example, to enable
132
+ lazy evaluation:
133
+
134
+ scheme = Heist::Runtime.new(:lazy => true)
135
+
136
+ The full list of options is:
137
+
138
+ * <tt>:continuations</tt> - sets whether continuations and <tt>(call/cc)</tt>
139
+ are enabled. Default is <tt>false</tt>.
140
+ * <tt>:lazy</tt> - sets whether evaluation is lazy. By default this option
141
+ is <tt>false</tt> and evaluation is eager.
142
+ * <tt>:unhygienic</tt> - set this to <tt>true</tt> to disable macro hygiene.
143
+
144
+ Bear in mind that continuations and lazy evaluation are not currently
145
+ compatible; enabling continuations forces eager evaluation. Also
146
+ note that lazy evaluation has a slightly unpredictable effect on the
147
+ use of macros.
148
+
149
+ Every runtime gets its own copy of all the built-in functions. You can add
150
+ your own functions written in Ruby and Heist will make them available as
151
+ Scheme procedures. For example, for running tests I do this in my +setup+
152
+ method:
153
+
154
+ @@env = Heist::Runtime.new
155
+ @@env.define('assert-equal') do |expected, actual|
156
+ assert_equal(expected, actual)
157
+ end
158
+
159
+ This lets me write, for example, <tt>(assert-equal 7 (+ 3 4))</tt> in my
160
+ tests. You can place arbitrary Ruby code in your functions, so this is
161
+ an easy way to expose Ruby functionality to the Scheme environment.
162
+
163
+ To run a Scheme file using the runtime, just call:
164
+
165
+ scheme.run("path/to/file.scm")
166
+
167
+
168
+ == Notes
169
+
170
+ This is alpha-quality software. While every attempt has been made to run
171
+ valid Scheme code, error handling is at present very weak. Anything
172
+ explicitly documented in this file can be assumed to work according to
173
+ the Scheme standard, or according to the information presented here,
174
+ and can be assumed to be reasonably stable.
175
+
176
+ I have not documented how to write your own syntax using Ruby because
177
+ it requires far too much knowledge of Heist's plumbing at present (I
178
+ suspect this may be unavoidable). Besides, we have macros so if you
179
+ want new syntax we've got you covered.
180
+
181
+ Heist is extremely liberal as regards symbols. Any sequence of
182
+ characters that does not contain any spaces or parentheses and that
183
+ is not a boolean literal, a number or a string is considered a valid
184
+ symbol. This means that syntax that should be used for chars and
185
+ vectors will currently parse as symbols.
186
+
187
+ There are currently no list operations. This may seem like a
188
+ staggering omission but my goal initially was to work through the
189
+ early stages of SICP, for which Heist in its current form is
190
+ adequate. Being something of a simpleton, I wrote the list representation
191
+ using arrays and it will need gutting and converting to linked lists
192
+ before list operations are implemented.
193
+
194
+ Macros are slightly more liberal than R5RS, in that this is a valid
195
+ macro:
196
+
197
+ (define-syntax my-let (syntax-rules ()
198
+ [(my-let (name value) ... body ...)
199
+ (let [(name value) ...]
200
+ body ...)]))
201
+
202
+ You are allowed to use ellipses in infix positions, as long as the
203
+ pattern that follows the ellipsis matches input that the preceeding
204
+ pattern would fail to match. This is, the ellipsis acts as a greedy
205
+ star operator in regular expressions. In the above, <tt>(name value) ...</tt>
206
+ captures input until a non-list expression is met, at which point
207
+ <tt>body ...</tt> takes over. The following expression evaluates
208
+ to <tt>3</tt> using this macro:
209
+
210
+ (my-let (x 2) (y 3) y)
211
+
212
+ Speaking of macros, there is no distinct macro expansion stage in
213
+ Heist. Macros are expanded as they are encountered at runtime, and
214
+ their expansions are inlined into the AST to improve performance:
215
+ the same macro use is never expanded more than once. Macros (and
216
+ native syntactic forms) are first-class, so they can be easily
217
+ aliased and used anonymously:
218
+
219
+ (define when if)
220
+
221
+ ((syntax-rules () [(_ expr) expr])
222
+ (+ 3 4)) ; => 7
223
+
224
+ It's not clear whether it would be useful to choose syntactic forms
225
+ conditionally, certainly this will not work with macros as the
226
+ inlining process will overwrite conditional code using the first
227
+ expansion generated.
228
+
229
+ Lazy evaluation mode is mainly useful for doing lambda calculus
230
+ without being concerned about the difference between normal and
231
+ applicative order, which for example affects the expression for
232
+ the Y combinator. It displays some interesting properties, such
233
+ as the fact that this is a perfectly reasonable definition for
234
+ <tt>Y</tt>:
235
+
236
+ (define (Y f)
237
+ (f (Y f)))
238
+ ; => #<procedure:Y>
239
+
240
+ (define fact (Y (lambda (rec)
241
+ (lambda (x)
242
+ (if (zero? x)
243
+ 1
244
+ (* x (rec (- x 1))))))))
245
+ ; => #<procedure:fact>
246
+
247
+ (fact 6)
248
+ ; => 720
249
+
250
+
251
+ == License
252
+
253
+ (The MIT License)
254
+
255
+ Copyright (c) 2009 James Coglan
256
+
257
+ Permission is hereby granted, free of charge, to any person obtaining
258
+ a copy of this software and associated documentation files (the
259
+ 'Software'), to deal in the Software without restriction, including
260
+ without limitation the rights to use, copy, modify, merge, publish,
261
+ distribute, sublicense, and/or sell copies of the Software, and to
262
+ permit persons to whom the Software is furnished to do so, subject to
263
+ the following conditions:
264
+
265
+ The above copyright notice and this permission notice shall be
266
+ included in all copies or substantial portions of the Software.
267
+
268
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
269
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
270
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
271
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
272
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
273
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
274
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/heist.rb'
6
+
7
+ Hoe.new('heist', Heist::VERSION) do |p|
8
+ p.developer('James Coglan', 'jcoglan@googlemail.com')
9
+ p.extra_deps = %w(oyster treetop)
10
+ end
11
+
12
+ # vim: syntax=Ruby
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/heist'
3
+ require File.dirname(__FILE__) + '/../lib/bin_spec'
4
+
5
+ begin
6
+ options = Heist::BIN_SPEC.parse
7
+ rescue Oyster::HelpRendered; exit
8
+ end
9
+
10
+ if options[:interactive] or options[:unclaimed].empty?
11
+ Heist::REPL.new(options).run
12
+ else
13
+ env = Heist::Runtime.new(options)
14
+ env.run(File.expand_path(options[:unclaimed].first))
15
+ end
16
+
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'oyster'
3
+
4
+ Heist::BIN_SPEC = Oyster.spec do
5
+ name "heist -- Ruby-powered Scheme interpreter, v. #{Heist::VERSION}"
6
+ author 'James Coglan <jcoglan@googlemail.com>'
7
+
8
+ synopsis <<-EOS
9
+ heist -i [OPTIONS]
10
+ heist FILE_NAME [OPTIONS]
11
+ EOS
12
+
13
+ flag :interactive, :desc =>
14
+ 'Start an interactive Scheme session'
15
+
16
+ flag :lazy, :default => false, :desc =>
17
+ 'Use lazy evaluation order'
18
+
19
+ flag :continuations, :default => false, :desc =>
20
+ 'Enable first-class continuations and (call/cc)'
21
+
22
+ flag :unhygienic, :default => false, :desc =>
23
+ 'Use Common Lisp-style unhygienic macros'
24
+ end
25
+
@@ -0,0 +1,95 @@
1
+ ; Any built-in functions that we can implement directly
2
+ ; in Scheme should go here. If at all possible, write
3
+ ; builtins in Scheme rather than Ruby.
4
+
5
+ ; (not x)
6
+ ; Boolean inverse of x
7
+ (define (not x)
8
+ (if x #f #t))
9
+
10
+ ; Longhand aliases for boolean constants
11
+ (define true #t)
12
+ (define false #f)
13
+
14
+ ; (boolean? x)
15
+ ; Returns true iff x is a boolean value
16
+ (define (boolean? x)
17
+ (or (eqv? x #t) (eqv? x #f)))
18
+
19
+ ; (number? x)
20
+ ; Returns true iff x is any type of number
21
+ (define number? complex?)
22
+
23
+ ; (zero? x)
24
+ ; Returns true iff x is zero
25
+ (define (zero? x)
26
+ (eqv? x 0))
27
+
28
+ ; (positive? x)
29
+ ; Returns true iff x > 0
30
+ (define (positive? x)
31
+ (> x 0))
32
+
33
+ ; (negative? x)
34
+ ; Returns true iff x < 0
35
+ (define (negative? x)
36
+ (< x 0))
37
+
38
+ ; (odd? x)
39
+ ; Returns true iff x is odd
40
+ (define (odd? x)
41
+ (= 1 (remainder x 2)))
42
+
43
+ ; (even? x)
44
+ ; Returns true iff x is even
45
+ (define (even? x)
46
+ (zero? (remainder x 2)))
47
+
48
+ ; (abs x)
49
+ ; Returns the absolute value of a number
50
+ (define (abs x)
51
+ (if (negative? x)
52
+ (- x)
53
+ x))
54
+
55
+ ; (gcd x y)
56
+ ; Returns the greatest common divisor of two numbers
57
+ ; http://en.wikipedia.org/wiki/Euclidean_algorithm
58
+ ; TODO take >2 arguments
59
+ (define (gcd x y)
60
+ (if (zero? y)
61
+ (abs x)
62
+ (gcd y (remainder x y))))
63
+
64
+ ; (lcm x y)
65
+ ; Returns the lowest common multiple of two numbers
66
+ ; http://en.wikipedia.org/wiki/Least_common_multiple
67
+ ; TODO take >2 arguments
68
+ (define (lcm x y)
69
+ (/ (abs (* x y))
70
+ (gcd x y)))
71
+
72
+ (define ceiling ceil)
73
+
74
+ ; (factorial x)
75
+ ; Returns factorial of x
76
+ (define (factorial x)
77
+ (define (iter y acc)
78
+ (if (zero? y)
79
+ acc
80
+ (iter (- y 1) (* y acc))))
81
+ (iter x 1))
82
+
83
+ ; (newline)
84
+ ; prints a new-line character
85
+ (define (newline)
86
+ (display "\n"))
87
+
88
+ ; (force)
89
+ ; Extracts the value of a promise created using (delay)
90
+ (define (force promise) (promise))
91
+
92
+ ; (call/cc)
93
+ ; Alias for (call-with-current-continuation)
94
+ (define call/cc call-with-current-continuation)
95
+