heist 0.2.1 → 0.3.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 +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
|
+
|