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,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
+