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.
@@ -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
- ("#t" / "#f") <Boolean>
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] .)* ignore
110
+ ";" (![\n\r] .)*
99
111
  end
100
112
  end
101
113
  end
@@ -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 = lambda do |prefix|
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
- puts "; => #{ result }\n\n" unless result.nil?
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
@@ -46,6 +46,7 @@ module Heist
46
46
  def to_s
47
47
  @expression.to_s
48
48
  end
49
+ alias :inspect :to_s
49
50
  end
50
51
 
51
52
  end
@@ -38,6 +38,7 @@ module Heist
38
38
  def to_s
39
39
  "#<continuation>"
40
40
  end
41
+ alias :inspect :to_s
41
42
  end
42
43
 
43
44
  end
@@ -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
- return nil unless consume[] or followed_by_ellipsis
177
- input_pair = input_pair.cdr
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.car == ELLIPSIS) && !ignoring_ellipses
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
@@ -37,6 +37,7 @@ module Heist
37
37
  def to_s
38
38
  "#<syntax:#{ @name }>"
39
39
  end
40
+ alias :inspect :to_s
40
41
  end
41
42
 
42
43
  end
@@ -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
+
@@ -16,19 +16,23 @@ module Heist
16
16
  #
17
17
  # Proper list: (1 2 3) Improper list: (1 2 3 . 4)
18
18
  #
19
- # # # # = cons
20
- # / \\ / \\ / = car
21
- # 1 # 1 # \\ = cdr
22
- # / \\ / \\ () = NULL
23
- # 2 # 2 #
24
- # / \\ / \\
25
- # 3 () 3 4
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 value") if frozen?
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 value") if frozen?
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 to_s
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 :inspect :to_s
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
+