heist 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+