rltk 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/LICENSE +27 -0
- data/README +386 -0
- data/Rakefile +67 -0
- data/lib/rltk/ast.rb +264 -0
- data/lib/rltk/cfg.rb +491 -0
- data/lib/rltk/lexer.rb +298 -0
- data/lib/rltk/lexers/calculator.rb +41 -0
- data/lib/rltk/lexers/ebnf.rb +40 -0
- data/lib/rltk/parser.rb +1354 -0
- data/lib/rltk/parsers/infix_calc.rb +43 -0
- data/lib/rltk/parsers/postfix_calc.rb +34 -0
- data/lib/rltk/parsers/prefix_calc.rb +34 -0
- data/lib/rltk/token.rb +66 -0
- data/test/tc_ast.rb +85 -0
- data/test/tc_cfg.rb +149 -0
- data/test/tc_lexer.rb +217 -0
- data/test/tc_parser.rb +275 -0
- data/test/tc_token.rb +34 -0
- metadata +87 -0
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
|