rltk 1.1.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/lib/rltk/ast.rb ADDED
@@ -0,0 +1,264 @@
1
+ # Author: Chris Wailes <chris.wailes@gmail.com>
2
+ # Project: Ruby Language Toolkit
3
+ # Date: 2011/01/19
4
+ # Description: This file provides a base Node class for ASTs.
5
+
6
+ module RLTK # :nodoc:
7
+ # A TypeMismatch is thrown when an object being set as a child or value of
8
+ # an ASTNode is of the wrong type.
9
+ class TypeMismatch < Exception
10
+
11
+ # Instantiates a new TypeMismatch object. The first argument is the
12
+ # expected type and the second argument is the actual type of the
13
+ # object.
14
+ def initialize(expected, actual)
15
+ @expected = expected
16
+ @actual = actual
17
+ end
18
+
19
+ # Converts the exception to a string.
20
+ def to_s
21
+ "Type Mismatch: Expected #{@expected} but received #{@actual}."
22
+ end
23
+ end
24
+
25
+ # Returns true if klass0 is a subclass of klass1; false otherwise.
26
+ def self.subclass_of?(klass0, klass1)
27
+ begin
28
+ return true if klass0 == klass1
29
+ end while klass0 = klass0.superclass
30
+
31
+ return false
32
+ end
33
+
34
+ # This class is a good start for all your abstract syntax tree node needs.
35
+ class ASTNode
36
+ # A reference to the parent node.
37
+ attr_accessor :parent
38
+
39
+ #################
40
+ # Class Methods #
41
+ #################
42
+
43
+ def ASTNode.inherited(klass)
44
+ klass.class_exec do
45
+ if self.superclass == ASTNode
46
+ @child_names = Array.new
47
+ @value_names = Array.new
48
+ else
49
+ @child_names = self.superclass.child_names.clone
50
+ @value_names = self.superclass.value_names.clone
51
+ end
52
+
53
+ # Defined a child for this AST class and its subclasses.
54
+ # The name of the child will be used to define accessor
55
+ # methods that include type checking. The type of this
56
+ # child must be a subclass of the ASTNode class.
57
+ def self.child(name, type)
58
+ if type.is_a?(Array) and type.length == 1
59
+ t = type.first
60
+
61
+ elsif type.is_a?(Class)
62
+ t = type
63
+
64
+ else
65
+ raise Exception, 'Child and Value types must be a class name or an array with a single class name element.'
66
+ end
67
+
68
+ # Check to make sure that type is a subclass of
69
+ # ASTNode.
70
+ if not RLTK::subclass_of?(t, ASTNode)
71
+ raise Exception, "A child's type specification must be a subclass of ASTNode."
72
+ end
73
+
74
+ @child_names << name
75
+ self.define_accessor(name, type, true)
76
+ end
77
+
78
+ # Returns an array of the names of this node's children.
79
+ def self.child_names
80
+ @child_names
81
+ end
82
+
83
+ # This method defines a type checking accessor named _name_
84
+ # with type _type_.
85
+ def self.define_accessor(name, type, set_parent = false)
86
+ ivar_name = ('@' + name.to_s).to_sym
87
+
88
+ define_method(name) do
89
+ self.instance_variable_get(ivar_name)
90
+ end
91
+
92
+ if type.is_a?(Class)
93
+ define_method((name.to_s + '=').to_sym) do |value|
94
+ if value.is_a?(type) or value == nil
95
+ self.instance_variable_set(ivar_name, value)
96
+
97
+ value.parent = self if value and set_parent
98
+ else
99
+ raise TypeMismatch.new(type, value.class)
100
+ end
101
+ end
102
+
103
+ else
104
+ type = type.first
105
+
106
+ define_method((name.to_s + '=').to_sym) do |value|
107
+ if value.inject(true) { |m, o| m and o.is_a?(type) }
108
+ self.instance_variable_set(ivar_name, value)
109
+
110
+ value.each { |c| c.parent = self } if set_parent
111
+ else
112
+ raise TypeMismatch.new(type, value.class)
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+
119
+ # Defined a value for this AST class and its subclasses.
120
+ # The name of the value will be used to define accessor
121
+ # methods that include type checking. The type of this
122
+ # value must NOT be a subclass of the ASTNode class.
123
+ def self.value(name, type)
124
+ if type.is_a?(Array) and type.length == 1
125
+ t = type.first
126
+
127
+ elsif type.is_a?(Class)
128
+ t = type
129
+
130
+ else
131
+ raise Exception, 'Child and Value types must be a class name or an array with a single class name element.'
132
+ end
133
+
134
+ # Check to make sure that type is NOT a subclass of
135
+ # ASTNode.
136
+ if RLTK::subclass_of?(t, ASTNode)
137
+ raise Exception, "A value's type specification must NOT be a subclass of ASTNode."
138
+ end
139
+
140
+ @value_names << name
141
+ self.define_accessor(name, type)
142
+ end
143
+
144
+ # Returns an array of the names of this node's values.
145
+ def self.value_names
146
+ @value_names
147
+ end
148
+ end
149
+ end
150
+
151
+ ####################
152
+ # Instance Methods #
153
+ ####################
154
+
155
+ # Used for AST comparison, this function will return true if the two
156
+ # nodes are of the same class and all of their values and children are
157
+ # equal.
158
+ def ==(other)
159
+ self.class == other.class and self.values == other.values and self.children == other.children
160
+ end
161
+
162
+ # Returns the note with name _key_.
163
+ def [](key)
164
+ @notes[key]
165
+ end
166
+
167
+ # Sets the note named _key_ to _value_.
168
+ def []=(key, value)
169
+ @notes[key] = value
170
+ end
171
+
172
+ # Returns an array of this node's children.
173
+ def children
174
+ self.class.child_names.map { |name| self.send(name) }
175
+ end
176
+
177
+ # Assigns an array of AST nodes as the children of this node.
178
+ def children=(children)
179
+ if children.length != self.class.child_names.length
180
+ raise Exception, 'Wrong number of children specified.'
181
+ end
182
+
183
+ self.class.child_names.each_with_index do |name, i|
184
+ self.send((name.to_s + '=').to_sym, children[i])
185
+ end
186
+ end
187
+
188
+ # Removes the note _key_ from this node. If the _recursive_ argument
189
+ # is true it will also remove the note from the node's children.
190
+ def delete_note(key, recursive = true)
191
+ if recursive
192
+ self.children.each do |child|
193
+ next if not child
194
+
195
+ if child.is_a?(Array)
196
+ child.each { |c| c.delete_note(key, true) }
197
+ else
198
+ child.delete_note(key, true)
199
+ end
200
+ end
201
+ end
202
+
203
+ @notes.delete(key)
204
+ end
205
+
206
+ # An iterator over the node's children.
207
+ def each
208
+ self.children.each { |c| yield c }
209
+ end
210
+
211
+ # Tests to see if a note named _key_ is present at this node.
212
+ def has_note?(key)
213
+ @notes.has_key?(key)
214
+ end
215
+
216
+ alias :'note?' :'has_note?'
217
+
218
+ # Instantiates a new ASTNode object. The arguments to this method are
219
+ # split into two lists: the set of values for this node and a list of
220
+ # its children. If the node has 2 values and 3 children you would
221
+ # pass the values in as the first two arguments (in the order they
222
+ # were declared) and then the children as the remaining arguments (in
223
+ # the order they were declared).
224
+ def initialize(*objects)
225
+ if self.class == RLTK::ASTNode
226
+ raise Exception, 'Attempting to instantiate the RLTK::ASTNode class.'
227
+ else
228
+ @notes = Hash.new()
229
+ @parent = nil
230
+
231
+ pivot = self.class.value_names.length
232
+
233
+ self.values = objects[0...pivot]
234
+ self.children = objects[pivot..-1]
235
+ end
236
+ end
237
+
238
+ # Maps the children of the ASTNode from one value to another.
239
+ def map
240
+ self.children = self.children.map { |c| yield c }
241
+ end
242
+
243
+ # Find the root of an AST.
244
+ def root
245
+ if @parent then @parent.root else self end
246
+ end
247
+
248
+ # Returns an array of this node's values.
249
+ def values
250
+ self.class.value_names.map { |name| self.send(name) }
251
+ end
252
+
253
+ # Assigns an array of objects as the values of this node.
254
+ def values=(values)
255
+ if values.length != self.class.value_names.length
256
+ raise Exception, 'Wrong number of values specified.'
257
+ end
258
+
259
+ self.class.value_names.each_with_index do |name, i|
260
+ self.send((name.to_s + '=').to_sym, values[i])
261
+ end
262
+ end
263
+ end
264
+ end