heist 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +17 -0
- data/Manifest.txt +5 -0
- data/README.txt +65 -51
- data/Rakefile +25 -1
- data/lib/builtin/library.scm +244 -9
- data/lib/builtin/primitives.rb +122 -10
- data/lib/builtin/syntax.scm +10 -14
- data/lib/heist.rb +5 -13
- data/lib/parser/nodes.rb +25 -5
- data/lib/parser/scheme.rb +394 -142
- data/lib/parser/scheme.tt +18 -6
- data/lib/repl.rb +19 -13
- data/lib/runtime/binding.rb +1 -0
- data/lib/runtime/callable/continuation.rb +1 -0
- data/lib/runtime/callable/macro.rb +37 -2
- data/lib/runtime/callable/macro/expansion.rb +34 -2
- data/lib/runtime/callable/syntax.rb +1 -0
- data/lib/runtime/data/character.rb +83 -0
- data/lib/runtime/data/cons.rb +17 -19
- data/lib/runtime/data/vector.rb +65 -0
- data/lib/runtime/frame.rb +7 -0
- data/lib/runtime/runtime.rb +2 -1
- data/lib/runtime/scope.rb +5 -9
- data/lib/runtime/stackless.rb +7 -0
- data/lib/trie.rb +141 -0
- data/test/equivalence.scm +7 -0
- data/test/let.scm +13 -0
- data/test/lists.scm +2 -3
- data/test/macros.scm +14 -0
- data/test/strings.scm +110 -0
- data/test/test_heist.rb +2 -0
- data/test/vectors.scm +83 -0
- metadata +10 -7
data/lib/parser/scheme.tt
CHANGED
@@ -10,7 +10,7 @@ module Heist
|
|
10
10
|
|
11
11
|
rule cell
|
12
12
|
ignore quote cell <QuotedCell> /
|
13
|
-
ignore (list / atom) ignore <Cell>
|
13
|
+
ignore (list / vector / atom) ignore <Cell>
|
14
14
|
end
|
15
15
|
|
16
16
|
rule quote
|
@@ -21,6 +21,10 @@ module Heist
|
|
21
21
|
"."
|
22
22
|
end
|
23
23
|
|
24
|
+
rule hash
|
25
|
+
"#"
|
26
|
+
end
|
27
|
+
|
24
28
|
rule list
|
25
29
|
("(" cells ")" / "[" cells "]") <List>
|
26
30
|
end
|
@@ -30,16 +34,20 @@ module Heist
|
|
30
34
|
cell* ignore
|
31
35
|
end
|
32
36
|
|
37
|
+
rule vector
|
38
|
+
(hash "(" cell* ignore ")" / hash "[" cell* ignore "]") <Vector>
|
39
|
+
end
|
40
|
+
|
33
41
|
rule atom
|
34
42
|
datum / identifier
|
35
43
|
end
|
36
44
|
|
37
45
|
rule datum
|
38
|
-
(boolean / number / string) !(!delimiter .) <Datum>
|
46
|
+
(boolean / number / character / string) !(!delimiter .) <Datum>
|
39
47
|
end
|
40
48
|
|
41
49
|
rule boolean
|
42
|
-
|
50
|
+
hash [tf] <Boolean>
|
43
51
|
end
|
44
52
|
|
45
53
|
rule number
|
@@ -62,6 +70,10 @@ module Heist
|
|
62
70
|
"-"? ("0" / [1-9] digit*) <Integer>
|
63
71
|
end
|
64
72
|
|
73
|
+
rule character
|
74
|
+
"#\\" glyph:(identifier / .) <Character>
|
75
|
+
end
|
76
|
+
|
65
77
|
rule string
|
66
78
|
'"' ('\\"' / [^"])* '"' <String>
|
67
79
|
end
|
@@ -79,7 +91,7 @@ module Heist
|
|
79
91
|
end
|
80
92
|
|
81
93
|
rule delimiter
|
82
|
-
quote / paren / space
|
94
|
+
quote / hash / paren / space
|
83
95
|
end
|
84
96
|
|
85
97
|
rule paren
|
@@ -91,11 +103,11 @@ module Heist
|
|
91
103
|
end
|
92
104
|
|
93
105
|
rule ignore
|
94
|
-
space* comment?
|
106
|
+
space* (comment ignore)?
|
95
107
|
end
|
96
108
|
|
97
109
|
rule comment
|
98
|
-
";" (![\n\r] .)*
|
110
|
+
";" (![\n\r] .)*
|
99
111
|
end
|
100
112
|
end
|
101
113
|
end
|
data/lib/repl.rb
CHANGED
@@ -6,6 +6,10 @@ module Heist
|
|
6
6
|
def initialize(options = {})
|
7
7
|
@runtime = Runtime.new(options)
|
8
8
|
@scope = Runtime::FileScope.new(@runtime.top_level, File.expand_path('.'))
|
9
|
+
@results = []
|
10
|
+
|
11
|
+
@runtime.define('~') { |x| @results[-x] }
|
12
|
+
|
9
13
|
reset!
|
10
14
|
end
|
11
15
|
|
@@ -13,18 +17,7 @@ module Heist
|
|
13
17
|
puts @runtime.info
|
14
18
|
|
15
19
|
Readline.completion_append_character = nil
|
16
|
-
Readline.completion_proc =
|
17
|
-
return nil if prefix == ''
|
18
|
-
matches = @runtime.top_level.grep(%r[^#{prefix}]i).
|
19
|
-
sort_by { |r| r.length }
|
20
|
-
return nil unless word = matches.first
|
21
|
-
while word.length > prefix.length
|
22
|
-
break if matches.all? { |m| m =~ %r[^#{word}]i }
|
23
|
-
word = word.gsub(/.$/, '')
|
24
|
-
end
|
25
|
-
return nil if word == prefix
|
26
|
-
word + (matches.size == 1 ? ' ' : '')
|
27
|
-
end
|
20
|
+
Readline.completion_proc = @runtime.top_level.method(:longest_prefix)
|
28
21
|
|
29
22
|
loop do
|
30
23
|
begin
|
@@ -37,7 +30,11 @@ module Heist
|
|
37
30
|
|
38
31
|
reset!
|
39
32
|
result = @scope.eval(tree)
|
40
|
-
|
33
|
+
unless result.nil?
|
34
|
+
puts "; => #{ text(result) }\n\n"
|
35
|
+
@results << result
|
36
|
+
@scope['^'] = result
|
37
|
+
end
|
41
38
|
|
42
39
|
rescue Exception => ex
|
43
40
|
return if SystemExit === ex
|
@@ -67,6 +64,15 @@ module Heist
|
|
67
64
|
@indent = 0
|
68
65
|
end
|
69
66
|
|
67
|
+
def text(object)
|
68
|
+
case object
|
69
|
+
when Runtime::Character, String then object.inspect
|
70
|
+
when TrueClass then '#t'
|
71
|
+
when FalseClass then '#f'
|
72
|
+
else object.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
70
76
|
def push(line)
|
71
77
|
old_depth = depth
|
72
78
|
@buffer << line
|
data/lib/runtime/binding.rb
CHANGED
@@ -48,6 +48,8 @@ module Heist
|
|
48
48
|
when Cons then
|
49
49
|
tail = pattern.each { |cell| pattern_vars(cell, excluded, results) }
|
50
50
|
pattern_vars(tail.cdr, excluded, results)
|
51
|
+
when Vector then
|
52
|
+
pattern.each { |cell| pattern_vars(cell, excluded, results) }
|
51
53
|
end
|
52
54
|
results
|
53
55
|
end
|
@@ -68,6 +70,7 @@ module Heist
|
|
68
70
|
def to_s
|
69
71
|
"#<macro:#{ @name }>"
|
70
72
|
end
|
73
|
+
alias :inspect :to_s
|
71
74
|
|
72
75
|
# Takes a +Cons+ expression and a +Scope+ (required for determining
|
73
76
|
# the binding of macro keywords), and returns a tuple containing an
|
@@ -173,8 +176,9 @@ module Heist
|
|
173
176
|
# using the current pattern until the pattern no
|
174
177
|
# longer matches the current input
|
175
178
|
#
|
176
|
-
|
177
|
-
|
179
|
+
consumed = consume[]
|
180
|
+
return nil unless consumed or followed_by_ellipsis
|
181
|
+
input_pair = input_pair.cdr if consumed
|
178
182
|
input_pair = input_pair.cdr while followed_by_ellipsis and consume[]
|
179
183
|
|
180
184
|
skip[]
|
@@ -186,6 +190,37 @@ module Heist
|
|
186
190
|
# input matches this object.
|
187
191
|
return nil unless rule_matches(scope, pattern_pair, input_pair, matches, depth)
|
188
192
|
|
193
|
+
when Vector then
|
194
|
+
# Fail if the pattern is a vector and the input is not
|
195
|
+
return nil unless Vector === input
|
196
|
+
|
197
|
+
# Iterate over the pattern and input, consuming input cells
|
198
|
+
# as we go. This is very similar to how we handle lists, we
|
199
|
+
# should probably refactor this.
|
200
|
+
input_index = 0
|
201
|
+
pattern.each_with_index do |token, pattern_index|
|
202
|
+
next if token == ELLIPSIS
|
203
|
+
|
204
|
+
followed_by_ellipsis = (pattern[pattern_index+1] == ELLIPSIS)
|
205
|
+
dx = followed_by_ellipsis ? 1 : 0
|
206
|
+
|
207
|
+
matches.descend!(Macro.pattern_vars(token, @formals),
|
208
|
+
depth + dx) if followed_by_ellipsis
|
209
|
+
|
210
|
+
consume = lambda do
|
211
|
+
not input[input_index].nil? and
|
212
|
+
rule_matches(scope, token, input[input_index],
|
213
|
+
matches, depth + dx)
|
214
|
+
end
|
215
|
+
|
216
|
+
consumed = consume[]
|
217
|
+
return nil unless consumed or followed_by_ellipsis
|
218
|
+
input_index += 1 if consumed
|
219
|
+
input_index += 1 while followed_by_ellipsis and consume[]
|
220
|
+
end
|
221
|
+
|
222
|
+
return nil unless input_index == input.size
|
223
|
+
|
189
224
|
# If the pattern is a formal keyword for the macro (a
|
190
225
|
# 'literal identifier' in the terms of the spec), return
|
191
226
|
# a boolean indicating whether the input is an identifier
|
@@ -60,6 +60,9 @@ module Heist
|
|
60
60
|
case template
|
61
61
|
|
62
62
|
when Cons then
|
63
|
+
# Return NULL if the template is an empty list
|
64
|
+
return Cons::NULL if template.null?
|
65
|
+
|
63
66
|
# If the template is a list opening with an ellipsis, expand
|
64
67
|
# the rest of the list, transcribing ellipses verbatim
|
65
68
|
return expand(template.cdr.car,
|
@@ -81,13 +84,16 @@ module Heist
|
|
81
84
|
end
|
82
85
|
|
83
86
|
# Iterate over the template, inserting matches as we go
|
84
|
-
while not template_pair.null?
|
87
|
+
while Cons === template_pair and not template_pair.null?
|
85
88
|
cell = template_pair.car
|
86
89
|
|
87
90
|
# Increment the repetition depth if the current subtemplate
|
88
91
|
# is followed by an ellipsis and we are not treating ellipses
|
89
92
|
# as literals
|
90
|
-
followed_by_ellipsis = (template_pair.cdr
|
93
|
+
followed_by_ellipsis = ( Cons === template_pair.cdr &&
|
94
|
+
template_pair.cdr.car == ELLIPSIS) &&
|
95
|
+
!ignoring_ellipses
|
96
|
+
|
91
97
|
dx = followed_by_ellipsis ? 1 : 0
|
92
98
|
|
93
99
|
repeater = cell if followed_by_ellipsis
|
@@ -109,6 +115,32 @@ module Heist
|
|
109
115
|
|
110
116
|
template_pair = template_pair.cdr
|
111
117
|
end
|
118
|
+
|
119
|
+
# Handle the tail of improper list templates
|
120
|
+
last.cdr = expand(template_pair, matches, depth, ignoring_ellipses) unless last.nil?
|
121
|
+
result
|
122
|
+
|
123
|
+
# TODO this is very similar to how we handle lists -> refactor
|
124
|
+
when Vector then
|
125
|
+
result, repeater = Vector.new, nil
|
126
|
+
push = lambda { |value| result << value }
|
127
|
+
|
128
|
+
template.each_with_index do |cell, template_index|
|
129
|
+
|
130
|
+
followed_by_ellipsis = (template[template_index+1] == ELLIPSIS) && !ignoring_ellipses
|
131
|
+
dx = followed_by_ellipsis ? 1 : 0
|
132
|
+
|
133
|
+
repeater = cell if followed_by_ellipsis
|
134
|
+
|
135
|
+
if cell == ELLIPSIS and not ignoring_ellipses
|
136
|
+
matches.expand!(repeater, depth + 1) do
|
137
|
+
push[expand(repeater, matches, depth + 1)]
|
138
|
+
end
|
139
|
+
else
|
140
|
+
push[expand(cell, matches, depth + dx,
|
141
|
+
ignoring_ellipses)] unless followed_by_ellipsis
|
142
|
+
end
|
143
|
+
end
|
112
144
|
result
|
113
145
|
|
114
146
|
when Identifier then
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
# The +Character+ class is used to represent Scheme's notion of characters,
|
5
|
+
# objects that represent single ASCII glyphs. They are written in Scheme code
|
6
|
+
# as the symbols <tt>#\\</tt> followed by the desired glyph.
|
7
|
+
#
|
8
|
+
# Characters can be mapped back and forth to integer character codes and are
|
9
|
+
# thus sortable. This class uses Ruby's +Comparable+ mixin to sort characters
|
10
|
+
# by their ASCII code.
|
11
|
+
#
|
12
|
+
# In addition to single glyphs, there are a few named characters that
|
13
|
+
# represent whitespace characters: see +SPECIAL+ for named characters
|
14
|
+
# supported by Heist.
|
15
|
+
#
|
16
|
+
class Character
|
17
|
+
include Comparable
|
18
|
+
attr_reader :glyph
|
19
|
+
|
20
|
+
# List of special character names and the strings they represent
|
21
|
+
SPECIAL = {
|
22
|
+
"space" => " ",
|
23
|
+
"newline" => "\n",
|
24
|
+
"tab" => "\t"
|
25
|
+
}
|
26
|
+
|
27
|
+
# A +Character+ is initialized using either a single-character string,
|
28
|
+
# or a string containing the name of a special whitespace character. If
|
29
|
+
# a name is used, it must appear as a key in +SPECIAL+ otherwise an
|
30
|
+
# exception is thrown.
|
31
|
+
def initialize(glyph)
|
32
|
+
raise SyntaxError.new("There is no character named '#{glyph}'") if glyph.size > 1 and SPECIAL[glyph].nil?
|
33
|
+
@glyph = glyph
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the ASCII code for the +Character+ as an integer.
|
37
|
+
def char_code
|
38
|
+
code, char = nil, to_s
|
39
|
+
char.each_byte { |x| code = x }
|
40
|
+
code
|
41
|
+
end
|
42
|
+
alias :to_i :char_code
|
43
|
+
|
44
|
+
# Returns +true+ iff the two characters represent the same glyph.
|
45
|
+
def ==(other)
|
46
|
+
Character === other and
|
47
|
+
( @glyph == other.glyph or
|
48
|
+
SPECIAL[@glyph] == other.glyph or
|
49
|
+
SPECIAL[other.glyph] == @glyph )
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the difference between the receiver's charcode and the argument's
|
53
|
+
# charcode, used for the +Comparable+ operations.
|
54
|
+
def <=>(other)
|
55
|
+
char_code - other.char_code
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a new +Character+ representing the uppercase version
|
59
|
+
# of the receiver.
|
60
|
+
def upcase
|
61
|
+
self.class.new(to_s.upcase)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a new +Character+ representing the lowercase version
|
65
|
+
# of the receiver.
|
66
|
+
def downcase
|
67
|
+
self.class.new(to_s.downcase)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a +String+ of unit length containing only the +Character+.
|
71
|
+
def to_s
|
72
|
+
SPECIAL[@glyph] || @glyph
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns a Scheme-style string representation of the +Character+.
|
76
|
+
def inspect
|
77
|
+
"#\\#{ @glyph }"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
data/lib/runtime/data/cons.rb
CHANGED
@@ -16,19 +16,23 @@ module Heist
|
|
16
16
|
#
|
17
17
|
# Proper list: (1 2 3) Improper list: (1 2 3 . 4)
|
18
18
|
#
|
19
|
-
#
|
20
|
-
# /
|
21
|
-
# 1
|
22
|
-
# /
|
23
|
-
# 2
|
24
|
-
# /
|
25
|
-
# 3
|
19
|
+
# . . . = cons
|
20
|
+
# / \ / \ / = car
|
21
|
+
# 1 . 1 . \ = cdr
|
22
|
+
# / \ / \ () = NULL
|
23
|
+
# 2 . 2 .
|
24
|
+
# / \ / \
|
25
|
+
# 3 () 3 4
|
26
|
+
#
|
27
|
+
# This also illustrates the relationship between dotted pairs and +Cons+
|
28
|
+
# cells; <tt>'(a . b)</tt> is equivalent to <tt>(cons 'a 'b)</tt>, and the
|
29
|
+
# list <tt>(1 2 3)</tt> is more fully written as <tt>(1 . (2 . (3 . ())))</tt>.
|
26
30
|
#
|
27
31
|
# +Cons+ objects are +Enumerable+, though always keep in mind that a
|
28
32
|
# +Cons+ does not 'contain' a whole list, it contains one value and a
|
29
33
|
# pointer to the rest of the list. Iterating on a +Cons+ involves
|
30
34
|
# walking this object graph.
|
31
|
-
|
35
|
+
#
|
32
36
|
class Cons
|
33
37
|
include Enumerable
|
34
38
|
include Expression
|
@@ -107,14 +111,14 @@ module Heist
|
|
107
111
|
# Sets the +car+ of the receiving +Cons+, unless it is frozen. If the
|
108
112
|
# given value is +nil+, +NULL+ is used instead.
|
109
113
|
def car=(car)
|
110
|
-
raise ImmutableError.new("Cannot modify constant
|
114
|
+
raise ImmutableError.new("Cannot modify list constant") if frozen?
|
111
115
|
@car = car.nil? ? NULL : car
|
112
116
|
end
|
113
117
|
|
114
118
|
# Sets the +cdr+ of the receiving +Cons+, unless it is frozen. If the
|
115
119
|
# given value is +nil+, +NULL+ is used instead.
|
116
120
|
def cdr=(cdr)
|
117
|
-
raise ImmutableError.new("Cannot modify constant
|
121
|
+
raise ImmutableError.new("Cannot modify list constant") if frozen?
|
118
122
|
@cdr = cdr.nil? ? NULL : cdr
|
119
123
|
end
|
120
124
|
|
@@ -214,19 +218,13 @@ module Heist
|
|
214
218
|
end
|
215
219
|
|
216
220
|
# Returns a Scheme-style string representation of the list.
|
217
|
-
def
|
221
|
+
def inspect
|
218
222
|
strings = []
|
219
|
-
tail = each { |value|
|
220
|
-
strings << case value
|
221
|
-
when String then value.inspect
|
222
|
-
when Symbol then "'#{value}"
|
223
|
-
else value
|
224
|
-
end
|
225
|
-
}.cdr
|
223
|
+
tail = each { |value| strings << value.inspect }.cdr
|
226
224
|
'(' + (strings * ' ') +
|
227
225
|
(tail == NULL ? '' : ' . ' + tail.to_s) + ')'
|
228
226
|
end
|
229
|
-
alias :
|
227
|
+
alias :to_s :inspect
|
230
228
|
end
|
231
229
|
|
232
230
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
# +Vector+ is Scheme's equivalent of Ruby's +Array+ class, and Heist
|
5
|
+
# implements it by subclassing +Array+. In Scheme code a vector is
|
6
|
+
# denoted as a proper list preceeded by a <tt>#</tt> symbol, for
|
7
|
+
# example <tt>#(1 2 3)</tt>. Vectors are flat, non-recursive data
|
8
|
+
# structures, meaning they are faster for accessing slots by numeric
|
9
|
+
# index since we do not have to walk a tree to find the correct node.
|
10
|
+
#
|
11
|
+
# As an example, you can think of the list <tt>(1 2 3)</tt> and the
|
12
|
+
# vector <tt>#(1 2 3 4)</tt> as having the following structure:
|
13
|
+
#
|
14
|
+
# Proper list: (1 2 3) Vector: #(1 2 3 4)
|
15
|
+
#
|
16
|
+
# . .
|
17
|
+
# / \ |
|
18
|
+
# 1 . -------------
|
19
|
+
# / \ | | | |
|
20
|
+
# 2 . 1 2 3 4
|
21
|
+
# / \
|
22
|
+
# 3 ()
|
23
|
+
#
|
24
|
+
# Accessing a member of a vector takes time independent of the member's
|
25
|
+
# index, whereas access time scales as O(n) for lists.
|
26
|
+
#
|
27
|
+
class Vector < Array
|
28
|
+
# A +Vector+ is initialized using a sequence of values, just like
|
29
|
+
# a Ruby +Array+. Optionally, it can be initialized using an array
|
30
|
+
# and a block, which will be used to map the array to new values
|
31
|
+
# before inserting into the +Vector+.
|
32
|
+
#
|
33
|
+
# Vector.new([1,2,3,4]) { |x| x*x }
|
34
|
+
# #=> #(1 2 3 4)
|
35
|
+
#
|
36
|
+
def initialize(*args, &block)
|
37
|
+
return super(*args) unless block_given?
|
38
|
+
args.first.each_with_index { |cell, i| self[i] = block.call(cell) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Performs a recursive freeze of the +Vector+ and all its members.
|
42
|
+
def freeze!
|
43
|
+
freeze
|
44
|
+
each { |slot| slot.freeze! if slot.respond_to?(:freeze!) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Sets the +value+ at the given +index+. Will throw an exception
|
48
|
+
# if the +Vector+ is frozen. The Scheme function <tt>(vector-set!)</tt>
|
49
|
+
# also performs out-of-bounds checks to keep vectors a fixed size,
|
50
|
+
# but this is not enforced at this level.
|
51
|
+
def []=(index, value)
|
52
|
+
raise ImmutableError.new("Cannot modify vector constant") if frozen?
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a Scheme-style string representation of the +Vector+.
|
57
|
+
def inspect
|
58
|
+
'#(' + map { |cell| cell.inspect }.join(' ') + ')'
|
59
|
+
end
|
60
|
+
alias :to_s :inspect
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|