rltk 2.2.1 → 3.0.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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +12 -12
  3. data/README.md +458 -285
  4. data/Rakefile +99 -92
  5. data/lib/rltk/ast.rb +221 -126
  6. data/lib/rltk/cfg.rb +218 -239
  7. data/lib/rltk/cg/basic_block.rb +1 -1
  8. data/lib/rltk/cg/bindings.rb +9 -26
  9. data/lib/rltk/cg/builder.rb +40 -8
  10. data/lib/rltk/cg/context.rb +1 -1
  11. data/lib/rltk/cg/contractor.rb +51 -0
  12. data/lib/rltk/cg/execution_engine.rb +45 -8
  13. data/lib/rltk/cg/function.rb +12 -2
  14. data/lib/rltk/cg/generated_bindings.rb +2541 -575
  15. data/lib/rltk/cg/generic_value.rb +2 -2
  16. data/lib/rltk/cg/instruction.rb +104 -83
  17. data/lib/rltk/cg/llvm.rb +44 -3
  18. data/lib/rltk/cg/memory_buffer.rb +22 -5
  19. data/lib/rltk/cg/module.rb +85 -36
  20. data/lib/rltk/cg/old_generated_bindings.rb +6152 -0
  21. data/lib/rltk/cg/pass_manager.rb +87 -43
  22. data/lib/rltk/cg/support.rb +2 -4
  23. data/lib/rltk/cg/target.rb +158 -28
  24. data/lib/rltk/cg/triple.rb +8 -8
  25. data/lib/rltk/cg/type.rb +69 -25
  26. data/lib/rltk/cg/value.rb +107 -66
  27. data/lib/rltk/cg.rb +16 -17
  28. data/lib/rltk/lexer.rb +21 -11
  29. data/lib/rltk/lexers/calculator.rb +1 -1
  30. data/lib/rltk/lexers/ebnf.rb +8 -7
  31. data/lib/rltk/parser.rb +300 -247
  32. data/lib/rltk/parsers/infix_calc.rb +1 -1
  33. data/lib/rltk/parsers/postfix_calc.rb +2 -2
  34. data/lib/rltk/parsers/prefix_calc.rb +2 -2
  35. data/lib/rltk/token.rb +1 -2
  36. data/lib/rltk/version.rb +3 -3
  37. data/lib/rltk.rb +6 -6
  38. data/test/cg/tc_basic_block.rb +83 -0
  39. data/test/cg/tc_control_flow.rb +191 -0
  40. data/test/cg/tc_function.rb +54 -0
  41. data/test/cg/tc_generic_value.rb +33 -0
  42. data/test/cg/tc_instruction.rb +256 -0
  43. data/test/cg/tc_llvm.rb +25 -0
  44. data/test/cg/tc_math.rb +88 -0
  45. data/test/cg/tc_module.rb +89 -0
  46. data/test/cg/tc_transforms.rb +68 -0
  47. data/test/cg/tc_type.rb +69 -0
  48. data/test/cg/tc_value.rb +151 -0
  49. data/test/cg/ts_cg.rb +23 -0
  50. data/test/tc_ast.rb +105 -8
  51. data/test/tc_cfg.rb +63 -48
  52. data/test/tc_lexer.rb +84 -96
  53. data/test/tc_parser.rb +224 -52
  54. data/test/tc_token.rb +6 -6
  55. data/test/ts_rltk.rb +12 -15
  56. metadata +149 -75
  57. data/lib/rltk/cg/generated_extended_bindings.rb +0 -287
  58. data/lib/rltk/util/abstract_class.rb +0 -25
  59. data/lib/rltk/util/monkeys.rb +0 -129
data/lib/rltk/ast.rb CHANGED
@@ -7,54 +7,62 @@
7
7
  # Requires #
8
8
  ############
9
9
 
10
- # Ruby Language Toolkit
11
- require 'rltk/util/monkeys'
10
+ # Gems
11
+ require 'filigree/abstract_class'
12
+ require 'filigree/class'
13
+ require 'filigree/match'
14
+ require 'filigree/types'
12
15
 
13
16
  #######################
14
17
  # Classes and Modules #
15
18
  #######################
16
19
 
17
- module RLTK # :nodoc:
18
- # A TypeMismatch is raised when an object being set as a child or value of
19
- # an ASTNode is of the wrong type.
20
- class TypeMismatch < StandardError
21
-
22
- # Instantiates a new TypeMismatch object.
23
- #
24
- # @param [Class] expected Expected type.
25
- # @param [Klass] actual Actual type of object.
26
- def initialize(expected, actual)
27
- @expected = expected
28
- @actual = actual
29
- end
30
-
31
- # @return [String] String representation of the error.
32
- def to_s
33
- "Type Mismatch: Expected #{@expected} but received #{@actual}."
34
- end
35
- end
36
-
20
+ module RLTK
37
21
  # This class is a good start for all your abstract syntax tree node needs.
38
22
  class ASTNode
39
- # @return [ASTNode] Reference to the parent node.
23
+
24
+ extend Filigree::AbstractClass
25
+ extend Filigree::Destructurable
26
+
27
+ # @return [ASTNode] Reference to the parent node.
40
28
  attr_accessor :parent
41
29
 
30
+ # @return [Hash] The notes hash for this node.
31
+ attr_reader :notes
32
+
42
33
  #################
43
34
  # Class Methods #
44
35
  #################
45
36
 
46
37
  class << self
47
38
 
39
+ # Check to make sure a name isn't re-defining a value or child.
40
+ #
41
+ # @raise [ArgumentError] Raised if the name is already used for an existing value or child
42
+ def check_odr(name)
43
+ if @child_names.include? name
44
+ raise ArgumentError, "Class #{self} or one of its superclasses already defines a child named #{name}"
45
+ end
46
+
47
+ if @value_names.include?(name)
48
+ raise ArgumentError, "Class #{self} or one of its superclasses already defines a value named #{name}"
49
+ end
50
+ end
51
+
48
52
  # Installs instance class varialbes into a class.
49
53
  #
50
54
  # @return [void]
51
55
  def install_icvars
52
56
  if self.superclass == ASTNode
53
57
  @child_names = Array.new
58
+ @child_types = Array.new
54
59
  @value_names = Array.new
60
+ @value_types = Array.new
55
61
  else
56
62
  @child_names = self.superclass.child_names.clone
63
+ @child_types = self.superclass.child_types.clone
57
64
  @value_names = self.superclass.value_names.clone
65
+ @value_types = self.superclass.value_types.clone
58
66
  end
59
67
  end
60
68
  protected :install_icvars
@@ -62,7 +70,7 @@ module RLTK # :nodoc:
62
70
  # Called when the Lexer class is sub-classed, it installes
63
71
  # necessary instance class variables.
64
72
  #
65
- # @param [Class] klass The class is inheriting from this class.
73
+ # @param [Class] klass The class is inheriting from this class.
66
74
  #
67
75
  # @return [void]
68
76
  def inherited(klass)
@@ -74,11 +82,13 @@ module RLTK # :nodoc:
74
82
  # methods that include type checking. The type of this
75
83
  # child must be a subclass of the ASTNode class.
76
84
  #
77
- # @param [String, Symbol] name Name of child node.
78
- # @param [Class] type Type of child node. Must be a subclass of ASTNode.
85
+ # @param [String, Symbol] name Name of child node.
86
+ # @param [Class] type Type of child node. Must be a subclass of ASTNode.
79
87
  #
80
88
  # @return [void]
81
89
  def child(name, type)
90
+ check_odr(name)
91
+
82
92
  if type.is_a?(Array) and type.length == 1
83
93
  t = type.first
84
94
 
@@ -96,20 +106,26 @@ module RLTK # :nodoc:
96
106
  end
97
107
 
98
108
  @child_names << name
109
+ @child_types << type
99
110
  define_accessor(name, type, true)
100
111
  end
101
112
 
102
- # @return [Array<Symbol>] Array of the names of this node class's children.
113
+ # @return [Array<Symbol>] Array of the names of this node class's children
103
114
  def child_names
104
115
  @child_names
105
116
  end
106
117
 
118
+ # @return [Array] Array of types of this node class's children
119
+ def child_types
120
+ @child_types
121
+ end
122
+
107
123
  # This method defines a type checking accessor named *name*
108
124
  # with type *type*.
109
125
  #
110
- # @param [String, Symbol] name Name of accessor.
111
- # @param [Class] type Class used for type checking.
112
- # @param [Boolean] set_parent Set the parent variable or not.
126
+ # @param [String, Symbol] name Name of accessor
127
+ # @param [Class] type Class used for type checking
128
+ # @param [Boolean] set_parent Set the parent variable or not
113
129
  #
114
130
  # @return [void]
115
131
  def define_accessor(name, type, set_parent = false)
@@ -122,48 +138,26 @@ module RLTK # :nodoc:
122
138
  if type.is_a?(Class)
123
139
  if set_parent
124
140
  define_method((name.to_s + '=').to_sym) do |value|
125
- if value.is_a?(type) or value == nil
126
- self.instance_variable_set(ivar_name, value)
127
-
128
- value.parent = self if value
129
- else
130
- raise TypeMismatch.new(type, value.class)
131
- end
141
+ self.instance_variable_set(ivar_name, check_type(value, type, nil, true))
142
+ value.parent = self if value
132
143
  end
133
144
 
134
145
  else
135
146
  define_method((name.to_s + '=').to_sym) do |value|
136
- if value.is_a?(type) or value == nil
137
- self.instance_variable_set(ivar_name, value)
138
-
139
- else
140
- raise TypeMismatch.new(type, value.class)
141
- end
147
+ self.instance_variable_set(ivar_name, check_type(value, type, nil, true))
142
148
  end
143
149
  end
144
150
 
145
151
  else
146
- type = type.first
147
-
148
152
  if set_parent
149
153
  define_method((name.to_s + '=').to_sym) do |value|
150
- if value.inject(true) { |m, o| m and o.is_a?(type) }
151
- self.instance_variable_set(ivar_name, value)
152
-
153
- value.each { |c| c.parent = self }
154
- else
155
- raise TypeMismatch.new(type, value.class)
156
- end
154
+ self.instance_variable_set(ivar_name, check_array_type(value, type.first, nil, true))
155
+ value.each { |c| c.parent = self }
157
156
  end
158
157
 
159
158
  else
160
159
  define_method((name.to_s + '=').to_sym) do |value|
161
- if value.inject(true) { |m, o| m and o.is_a?(type) }
162
- self.instance_variable_set(ivar_name, value)
163
-
164
- else
165
- raise TypeMismatch.new(type, value.class)
166
- end
160
+ self.instance_variable_set(ivar_name, check_array_type(value, type.first, nil, true))
167
161
  end
168
162
  end
169
163
  end
@@ -172,14 +166,15 @@ module RLTK # :nodoc:
172
166
 
173
167
  # Defined a value for this AST class and its subclasses.
174
168
  # The name of the value will be used to define accessor
175
- # methods that include type checking. The type of this
176
- # value must NOT be a subclass of the ASTNode class.
169
+ # methods that include type checking.
177
170
  #
178
- # @param [String, Symbol] name Name of value.
179
- # @param [Class] type Type of value. Must NOT be a subclass of ASTNode.
171
+ # @param [String, Symbol] name Name of value
172
+ # @param [Class] type Type of value
180
173
  #
181
174
  # @return [void]
182
175
  def value(name, type)
176
+ check_odr(name)
177
+
183
178
  if type.is_a?(Array) and type.length == 1
184
179
  t = type.first
185
180
 
@@ -190,20 +185,20 @@ module RLTK # :nodoc:
190
185
  raise 'Child and Value types must be a class name or an array with a single class name element.'
191
186
  end
192
187
 
193
- # Check to make sure that type is NOT a subclass of
194
- # ASTNode.
195
- if t.subclass_of?(ASTNode)
196
- raise "A value's type specification must NOT be a subclass of ASTNode."
197
- end
198
-
199
188
  @value_names << name
189
+ @value_types << type
200
190
  define_accessor(name, type)
201
191
  end
202
192
 
203
- # @return [Array<Symbol>] Array of the names of this node class's values.
193
+ # @return [Array<Symbol>] Array of the names of this node class's values
204
194
  def value_names
205
195
  @value_names
206
196
  end
197
+
198
+ # @return [Array<Symbol>] Array of the types of this node class's values
199
+ def value_types
200
+ @value_types
201
+ end
207
202
  end
208
203
 
209
204
  ####################
@@ -214,14 +209,14 @@ module RLTK # :nodoc:
214
209
  # nodes are of the same class and all of their values and children
215
210
  # are equal.
216
211
  #
217
- # @param [ASTNode] other The ASTNode to compare to.
212
+ # @param [ASTNode] other The ASTNode to compare to
218
213
  #
219
214
  # @return [Boolean]
220
215
  def ==(other)
221
216
  self.class == other.class and self.values == other.values and self.children == other.children
222
217
  end
223
218
 
224
- # @return [Object] Note with the name *key*.
219
+ # @return [Object] Note with the name *key*
225
220
  def [](key)
226
221
  @notes[key]
227
222
  end
@@ -231,31 +226,71 @@ module RLTK # :nodoc:
231
226
  @notes[key] = value
232
227
  end
233
228
 
234
- # @return [Array<ASTNode>] Array of this node's children.
235
- def children
236
- self.class.child_names.map { |name| self.send(name) }
229
+ # This method allows ASTNodes to be destructured for pattern matching.
230
+ def call(arity)
231
+ if arity == self.values.length
232
+ self.values
233
+ else
234
+ [*self.values, *self.children]
235
+ end
236
+ end
237
+
238
+ # @param [Class] as The type that should be returned by the method. Must be either Array or hash.
239
+ #
240
+ # @return [Array<ASTNode>, Hash{Symbol => ASTNode}] Array or Hash of this node's children.
241
+ def children(as = Array)
242
+ if as == Array
243
+ self.class.child_names.map { |name| self.send(name) }
244
+
245
+ elsif as == Hash
246
+ self.class.child_names.inject(Hash.new) { |h, name| h[name] = self.send(name); h }
247
+
248
+ else
249
+ raise 'Children can only be returned as an Array or a Hash.'
250
+ end
237
251
  end
238
252
 
239
- # Assigns an array of AST nodes as the children of this node.
253
+ # Assigns an array or hash of AST nodes as the children of this node.
254
+ # If a hash is provided as an argument the key is used as the name of
255
+ # the child a object should be assigned to.
240
256
  #
241
- # @param [Array<ASTNode>] children Children to be assigned to this node.
257
+ # @param [Array<ASTNode>, Hash{Symbol => ASTNode}] children Children to be assigned to this node.
242
258
  #
243
259
  # @return [void]
244
260
  def children=(children)
245
- if children.length != self.class.child_names.length
246
- raise 'Wrong number of children specified.'
247
- end
261
+ case children
262
+ when Array
263
+ if children.length != self.class.child_names.length
264
+ raise 'Wrong number of children specified.'
265
+ end
248
266
 
249
- self.class.child_names.each_with_index do |name, i|
250
- self.send((name.to_s + '=').to_sym, children[i])
267
+ self.class.child_names.each_with_index do |name, i|
268
+ self.send((name.to_s + '=').to_sym, children[i])
269
+ end
270
+
271
+ when Hash
272
+ children.each do |name, val|
273
+ if self.class.child_names.include?(name)
274
+ self.send((name.to_s + '=').to_sym, val)
275
+ else
276
+ raise "ASTNode subclass #{self.class.name} does not have a child named #{name}."
277
+ end
278
+ end
251
279
  end
252
280
  end
253
281
 
282
+ # Produce an exact copy of this tree.
283
+ #
284
+ # @return [ASTNode] A copy of the tree.
285
+ def copy
286
+ self.map { |c| c }
287
+ end
288
+
254
289
  # Removes the note *key* from this node. If the *recursive* argument
255
290
  # is true it will also remove the note from the node's children.
256
291
  #
257
- # @param [Object] key The key of the note to remove.
258
- # @param [Boolean] recursive Do a recursive removal or not.
292
+ # @param [Object] key The key of the note to remove
293
+ # @param [Boolean] recursive Do a recursive removal or not
259
294
  def delete_note(key, recursive = true)
260
295
  if recursive
261
296
  self.children.each do |child|
@@ -276,16 +311,17 @@ module RLTK # :nodoc:
276
311
  # to serialize an AST. You can use Marshal.load to reconstruct a
277
312
  # serialized AST.
278
313
  #
279
- # @param [nil, IO, String] dest Where the serialized version of the AST will end up. If nil, this method will return the AST as a string.
280
- # @param [Fixnum] limit Recursion depth. If -1 is specified there is no limit on the recursion depth.
314
+ # @param [nil, IO, String] dest Where the serialized version of the AST will end up. If nil,
315
+ # this method will return the AST as a string.
316
+ # @param [Fixnum] limit Recursion depth. If -1 is specified there is no limit on the recursion depth.
281
317
  #
282
- # @return [void, String] String if *dest* is nil, void otherwise.
318
+ # @return [void, String] String if *dest* is nil, void otherwise.
283
319
  def dump(dest = nil, limit = -1)
284
320
  case dest
285
- when nil then Marshal.dump(self, limit)
286
- when String then File.open(dest, 'w') { |f| Marshal.dump(self, f, limit) }
287
- when IO then Marshal.dump(self, dest, limit)
288
- else raise TypeError, "AST#dump expects nil, a String, or an IO object for the dest parameter."
321
+ when nil then Marshal.dump(self, limit)
322
+ when String then File.open(dest, 'w') { |f| Marshal.dump(self, f, limit) }
323
+ when IO then Marshal.dump(self, dest, limit)
324
+ else raise TypeError, "AST#dump expects nil, a String, or an IO object for the dest parameter."
289
325
  end
290
326
  end
291
327
 
@@ -296,16 +332,18 @@ module RLTK # :nodoc:
296
332
  # * Post-order (:post)
297
333
  # * Level-order (:level)
298
334
  #
335
+ # @param [:pre, :post, :level] order The order in which to iterate over the tree
336
+ #
299
337
  # @return [void]
300
338
  def each(order = :pre, &block)
301
339
  case order
302
340
  when :pre
303
341
  yield self
304
342
 
305
- self.children.compact.each { |c| c.each(:pre, &block) }
343
+ self.children.flatten.compact.each { |c| c.each(:pre, &block) }
306
344
 
307
345
  when :post
308
- self.children.compact.each { |c| c.each(:post, &block) }
346
+ self.children.flatten.compact.each { |c| c.each(:post, &block) }
309
347
 
310
348
  yield self
311
349
 
@@ -315,7 +353,7 @@ module RLTK # :nodoc:
315
353
  while node = level_queue.shift
316
354
  yield node
317
355
 
318
- level_queue += node.children.compact
356
+ level_queue += node.children.flatten.compact
319
357
  end
320
358
  end
321
359
  end
@@ -334,25 +372,31 @@ module RLTK # :nodoc:
334
372
  # the order they were declared).
335
373
  #
336
374
  # If a node has 2 values and 2 children and is passed only a single
337
- # value the remaining values and children are assumed to be nil.
375
+ # value the remaining values and children are assumed to be nil or
376
+ # empty arrays, depending on the declared type of the value or
377
+ # child.
338
378
  #
339
- # @param [Array<Object>] objects The values and children of this node.
340
- def initialize(*objects)
341
- if self.class == RLTK::ASTNode
342
- raise 'Attempting to instantiate the RLTK::ASTNode class.'
343
- else
344
- @notes = Hash.new()
345
- @parent = nil
346
-
347
- # Pad out the objects array with nil values.
348
- max_args = self.class.value_names.length + self.class.child_names.length
349
- objects.fill(nil, objects.length...max_args)
350
-
351
- pivot = self.class.value_names.length
352
-
353
- self.values = objects[0...pivot]
354
- self.children = objects[pivot..-1]
355
- end
379
+ # If a block is passed to initialize the block will be executed in
380
+ # the conext of the new object.
381
+ #
382
+ # @param [Array<Object>] objects Values and children of this node
383
+ def initialize(*objects, &block)
384
+ @notes = Hash.new()
385
+ @parent = nil
386
+
387
+ # Pad out the objects array with nil values and empty
388
+ # arrays.
389
+ all_types = self.class.value_types + self.class.child_types
390
+ remaining_types = all_types[objects.length..-1]
391
+
392
+ objects += remaining_types.map { |type| type.is_a?(Array) ? [] : nil }
393
+
394
+ pivot = self.class.value_names.length
395
+
396
+ self.values = objects[0...pivot]
397
+ self.children = objects[pivot..-1]
398
+
399
+ self.instance_exec(&block) if not block.nil?
356
400
  end
357
401
 
358
402
  # Create a new tree by using the provided Proc object to map the
@@ -362,10 +406,21 @@ module RLTK # :nodoc:
362
406
  #
363
407
  # @note This does not modify the current tree.
364
408
  #
365
- # @return [Object] The result of calling the given block on the root node.
409
+ # @return [Object] Result of calling the given block on the root node
366
410
  def map(&block)
367
- new_children = self.children.map { |c| if c.nil? then block.call(c) else c.map(&block) end }
368
- new_node = self.class.new(*self.values, *new_children)
411
+ new_values = self.values.map { |v| v.clone }
412
+
413
+ new_children =
414
+ self.children.map do |c0|
415
+ case c0
416
+ when Array then c0.map { |c1| c1.map(&block) }
417
+ when ASTNode then c0.map(&block)
418
+ when NilClass then nil
419
+ end
420
+ end
421
+
422
+ new_node = self.class.new(*new_values, *new_children)
423
+ new_node.notes = self.notes
369
424
 
370
425
  block.call(new_node)
371
426
  end
@@ -378,33 +433,73 @@ module RLTK # :nodoc:
378
433
  # calling the provided block on the root node is used as the
379
434
  # return value.
380
435
  #
381
- # @return [Object] The result of calling the given block on the root node.
436
+ # @return [Object] Result of calling the given block on the root node
382
437
  def map!(&block)
383
- self.children = self.children.map { |c| if c.nil? then block.call(c) else c.map!(&block) end }
438
+ self.children =
439
+ self.children.map do |c0|
440
+ case c0
441
+ when Array then c0.map { |c1| c1.map!(&block) }
442
+ when ASTNode then c0.map!(&block)
443
+ when NilClass then nil
444
+ end
445
+ end
384
446
 
385
447
  block.call(self)
386
448
  end
387
449
 
450
+ # Set the notes for this node from a given hash.
451
+ #
452
+ # @param [Hash] new_notes The new notes for this node.
453
+ #
454
+ # @return [void]
455
+ def notes=(new_notes)
456
+ @notes = new_notes.clone
457
+ end
458
+
388
459
  # @return [ASTNode] Root of the abstract syntax tree.
389
460
  def root
390
461
  if @parent then @parent.root else self end
391
462
  end
392
463
 
393
- # @return [Array<Object>] Array of this node's values.
394
- def values
395
- self.class.value_names.map { |name| self.send(name) }
464
+ # @param [Class] as The type that should be returned by the method. Must be either Array or hash.
465
+ #
466
+ # @return [Array<Object>, Hash{Symbol => Object}] Array or Hash of this node's values.
467
+ def values(as = Array)
468
+ if as == Array
469
+ self.class.value_names.map { |name| self.send(name) }
470
+
471
+ elsif as == Hash
472
+ self.class.value_names.inject(Hash.new) { |h, name| h[name] = self.send(name); h }
473
+
474
+ else
475
+ raise 'Values can only be returned as an Array or a Hash.'
476
+ end
396
477
  end
397
478
 
398
- # Assigns an array of objects as the values of this node.
479
+ # Assigns an array or hash of objects as the values of this node. If
480
+ # a hash is provided as an argument the key is used as the name of
481
+ # the value an object should be assigned to.
399
482
  #
400
- # @param [Array<Object>] values The values to be assigned to this node.
483
+ # @param [Array<Object>, Hash{Symbol => Object}] values The values to be assigned to this node.
401
484
  def values=(values)
402
- if values.length != self.class.value_names.length
403
- raise 'Wrong number of values specified.'
404
- end
485
+ case values
486
+ when Array
487
+ if values.length != self.class.value_names.length
488
+ raise 'Wrong number of values specified.'
489
+ end
405
490
 
406
- self.class.value_names.each_with_index do |name, i|
407
- self.send((name.to_s + '=').to_sym, values[i])
491
+ self.class.value_names.each_with_index do |name, i|
492
+ self.send((name.to_s + '=').to_sym, values[i])
493
+ end
494
+
495
+ when Hash
496
+ values.each do |name, val|
497
+ if self.class.value_names.include?(name)
498
+ self.send((name.to_s + '=').to_sym, val)
499
+ else
500
+ raise "ASTNode subclass #{self.class.name} does not have a value named #{name}."
501
+ end
502
+ end
408
503
  end
409
504
  end
410
505
  end