rltk 1.1.0

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