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/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
|