sexp_processor 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,15 @@
1
+ === 3.0.0 / 2008-10-22
2
+
3
+ * 2 major enhancements:
4
+
5
+ * Released as its own project, splitting from ParseTree
6
+ * Added Environment to SexpProcessor and built it in. YAY!
7
+
8
+ * 6 minor enhancements:
9
+
10
+ * Allowed CompositeSexpProcessor to be more ducktypey.
11
+ * Refactored Sexp#method_missing into find_node and find_nodes.
12
+ * Removed Sexp#for and other PT specific code.
13
+ * SexpProcessor#process now runs rewriters before everything else.
14
+ * SexpProcessor#rewrite context only for subs, EMPTY for top level rewrites.
15
+ * SexpProcessor#rewrite will stop iterating if the result isn't another Sexp.
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/composite_sexp_processor.rb
6
+ lib/sexp.rb
7
+ lib/sexp_processor.rb
8
+ test/test_composite_sexp_processor.rb
9
+ test/test_environment.rb
10
+ test/test_sexp.rb
11
+ test/test_sexp_processor.rb
data/README.txt ADDED
@@ -0,0 +1,48 @@
1
+ = SexpProcessor
2
+
3
+ * FIX (url)
4
+
5
+ == DESCRIPTION:
6
+
7
+ FIX (describe your package)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIX (list of features or problems)
12
+
13
+ == SYNOPSIS:
14
+
15
+ FIX (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * FIX (list of requirements)
20
+
21
+ == INSTALL:
22
+
23
+ * FIX (sudo gem install, anything else)
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2008 FIX
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.add_include_dirs "lib"
7
+
8
+ require './lib/sexp_processor.rb'
9
+
10
+ Hoe.new('sexp_processor', SexpProcessor::VERSION) do |sexp|
11
+ sexp.rubyforge_name = 'parsetree'
12
+ sexp.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
13
+ end
14
+
15
+ # vim: syntax=Ruby
@@ -0,0 +1,49 @@
1
+ require 'sexp_processor'
2
+
3
+ ##
4
+ # Implements the Composite pattern on SexpProcessor. Need we say more?
5
+ #
6
+ # Yeah... probably. Implements a SexpProcessor of SexpProcessors so
7
+ # you can easily chain multiple to each other. At some stage we plan
8
+ # on having all of them run +process+ and but only ever output
9
+ # something when +generate+ is called, allowing for deferred final
10
+ # processing.
11
+
12
+ class CompositeSexpProcessor < SexpProcessor
13
+
14
+ ##
15
+ # The list o' processors to run.
16
+
17
+ attr_reader :processors
18
+
19
+ def initialize # :nodoc:
20
+ super
21
+ @processors = []
22
+ end
23
+
24
+ ##
25
+ # Add a +processor+ to the list of processors to run.
26
+
27
+ def <<(processor)
28
+ raise ArgumentError, "Can only add sexp processors" unless
29
+ SexpProcessor === processor || processor.respond_to?(:process)
30
+ @processors << processor
31
+ end
32
+
33
+ ##
34
+ # Run +exp+ through all of the processors, returning the final
35
+ # result.
36
+
37
+ def process(exp)
38
+ @processors.each do |processor|
39
+ exp = processor.process(exp)
40
+ end
41
+ exp
42
+ end
43
+
44
+ def on_error_in(node_type, &block)
45
+ @processors.each do |processor|
46
+ processor.on_error_in(node_type, &block)
47
+ end
48
+ end
49
+ end
data/lib/sexp.rb ADDED
@@ -0,0 +1,270 @@
1
+
2
+ $TESTING ||= false # unless defined $TESTING
3
+
4
+ ##
5
+ # Sexps are the basic storage mechanism of SexpProcessor. Sexps have
6
+ # a +type+ (to be renamed +node_type+) which is the first element of
7
+ # the Sexp. The type is used by SexpProcessor to determine whom to
8
+ # dispatch the Sexp to for processing.
9
+
10
+ class Sexp < Array # ZenTest FULL
11
+
12
+ @@array_types = [ :array, :args, ]
13
+
14
+ ##
15
+ # Create a new Sexp containing +args+.
16
+
17
+ def initialize(*args)
18
+ super(args)
19
+ end
20
+
21
+ ##
22
+ # Creates a new Sexp from Array +a+.
23
+
24
+ def self.from_array(a)
25
+ ary = Array === a ? a : [a]
26
+
27
+ result = self.new
28
+
29
+ ary.each do |x|
30
+ case x
31
+ when Sexp
32
+ result << x
33
+ when Array
34
+ result << self.from_array(x)
35
+ else
36
+ result << x
37
+ end
38
+ end
39
+
40
+ result
41
+ end
42
+
43
+ def ==(obj) # :nodoc:
44
+ if obj.class == self.class then
45
+ super
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ ##
52
+ # Returns true if this Sexp's pattern matches +sexp+.
53
+
54
+ def ===(sexp)
55
+ return nil unless Sexp === sexp
56
+ pattern = self # this is just for my brain
57
+
58
+ return true if pattern == sexp
59
+
60
+ sexp.each do |subset|
61
+ return true if pattern === subset
62
+ end
63
+
64
+ return nil
65
+ end
66
+
67
+ ##
68
+ # Returns true if this Sexp matches +pattern+. (Opposite of #===.)
69
+
70
+ def =~(pattern)
71
+ return pattern === self
72
+ end
73
+
74
+ ##
75
+ # Returns true if the node_type is +array+ or +args+.
76
+ #
77
+ # REFACTOR: to TypedSexp - we only care when we have units.
78
+
79
+ def array_type?
80
+ type = self.first
81
+ @@array_types.include? type
82
+ end
83
+
84
+ def compact # :nodoc:
85
+ self.delete_if { |o| o.nil? }
86
+ end
87
+
88
+ ##
89
+ # Enumeratates the sexp yielding to +b+ when the node_type == +t+.
90
+
91
+ def each_of_type(t, &b)
92
+ each do | elem |
93
+ if Sexp === elem then
94
+ elem.each_of_type(t, &b)
95
+ b.call(elem) if elem.first == t
96
+ end
97
+ end
98
+ end
99
+
100
+ ##
101
+ # Replaces all elements whose node_type is +from+ with +to+. Used
102
+ # only for the most trivial of rewrites.
103
+
104
+ def find_and_replace_all(from, to)
105
+ each_with_index do | elem, index |
106
+ if Sexp === elem then
107
+ elem.find_and_replace_all(from, to)
108
+ else
109
+ self[index] = to if elem == from
110
+ end
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Replaces all Sexps matching +pattern+ with Sexp +repl+.
116
+
117
+ def gsub(pattern, repl)
118
+ return repl if pattern == self
119
+
120
+ new = self.map do |subset|
121
+ case subset
122
+ when Sexp then
123
+ subset.gsub(pattern, repl)
124
+ else
125
+ subset
126
+ end
127
+ end
128
+
129
+ return Sexp.from_array(new)
130
+ end
131
+
132
+ def inspect # :nodoc:
133
+ sexp_str = self.map {|x|x.inspect}.join(', ')
134
+ return "s(#{sexp_str})"
135
+ end
136
+
137
+ def find_node name, delete = false
138
+ matches = find_nodes name
139
+
140
+ case matches.size
141
+ when 0 then
142
+ nil
143
+ when 1 then
144
+ match = matches.first
145
+ delete match if delete
146
+ match
147
+ else
148
+ raise NoMethodError, "multiple nodes for #{name} were found in #{inspect}"
149
+ end
150
+ end
151
+
152
+ def find_nodes name
153
+ find_all { | sexp | Sexp === sexp and sexp.first == name }
154
+ end
155
+
156
+ ##
157
+ # Returns the node named +node+, deleting it if +delete+ is true.
158
+
159
+ def method_missing meth, delete = false
160
+ find_node meth, delete
161
+ end
162
+
163
+ def pretty_print(q) # :nodoc:
164
+ q.group(1, 's(', ')') do
165
+ q.seplist(self) {|v| q.pp v }
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Returns the Sexp without the node_type.
171
+
172
+ def sexp_body
173
+ self[1..-1]
174
+ end
175
+
176
+ ##
177
+ # If run with debug, Sexp will raise if you shift on an empty
178
+ # Sexp. Helps with debugging.
179
+
180
+ def shift
181
+ raise "I'm empty" if self.empty?
182
+ super
183
+ end if $DEBUG or $TESTING
184
+
185
+ ##
186
+ # Returns the bare bones structure of the sexp.
187
+ # s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
188
+
189
+ def structure
190
+ result = self.class.new
191
+ if Array === self.first then
192
+ result = self.first.structure
193
+ else
194
+ result << self.first
195
+ self.grep(Array).each do |subexp|
196
+ result << subexp.structure
197
+ end
198
+ end
199
+ result
200
+ end
201
+
202
+ ##
203
+ # Replaces the Sexp matching +pattern+ with +repl+.
204
+
205
+ def sub(pattern, repl)
206
+ return repl.dup if pattern == self
207
+
208
+ done = false
209
+
210
+ new = self.map do |subset|
211
+ if done then
212
+ subset
213
+ else
214
+ case subset
215
+ when Sexp then
216
+ if pattern == subset then
217
+ done = true
218
+ repl.dup
219
+ elsif pattern === subset then
220
+ done = true
221
+ subset.sub pattern, repl
222
+ else
223
+ subset
224
+ end
225
+ else
226
+ subset
227
+ end
228
+ end
229
+ end
230
+
231
+ return Sexp.from_array(new)
232
+ end
233
+
234
+ def to_a # :nodoc:
235
+ self.map { |o| Sexp === o ? o.to_a : o }
236
+ end
237
+
238
+ def to_s # :nodoc:
239
+ inspect
240
+ end
241
+
242
+ end
243
+
244
+ class SexpMatchSpecial < Sexp; end
245
+
246
+ class SexpAny < SexpMatchSpecial
247
+ def ==(o)
248
+ Sexp === o
249
+ end
250
+
251
+ def ===(o)
252
+ return Sexp === o
253
+ end
254
+
255
+ def inspect
256
+ "ANY"
257
+ end
258
+ end
259
+
260
+ module SexpMatchSpecials
261
+ def ANY(); return SexpAny.new; end
262
+ end
263
+
264
+ ##
265
+ # This is just a stupid shortcut to make indentation much cleaner.
266
+
267
+ def s(*args)
268
+ Sexp.new(*args)
269
+ end
270
+
@@ -0,0 +1,407 @@
1
+
2
+ $TESTING = false unless defined? $TESTING
3
+
4
+ require 'sexp'
5
+
6
+ ##
7
+ # SexpProcessor provides a uniform interface to process Sexps.
8
+ #
9
+ # In order to create your own SexpProcessor subclass you'll need
10
+ # to call super in the initialize method, then set any of the
11
+ # Sexp flags you want to be different from the defaults.
12
+ #
13
+ # SexpProcessor uses a Sexp's type to determine which process method
14
+ # to call in the subclass. For Sexp <code>s(:lit, 1)</code>
15
+ # SexpProcessor will call #process_lit, if it is defined.
16
+ #
17
+ # You can also specify a default method to call for any Sexp types
18
+ # without a process_<type> method or use the default processor provided to
19
+ # skip over them.
20
+ #
21
+ # Here is a simple example:
22
+ #
23
+ # class MyProcessor < SexpProcessor
24
+ # def initialize
25
+ # super
26
+ # self.strict = false
27
+ # end
28
+ #
29
+ # def process_lit(exp)
30
+ # val = exp.shift
31
+ # return val
32
+ # end
33
+ # end
34
+
35
+ class SexpProcessor
36
+
37
+ VERSION = '3.0.0'
38
+
39
+ ##
40
+ # Automatically shifts off the Sexp type before handing the
41
+ # Sexp to process_<type>
42
+
43
+ attr_accessor :auto_shift_type
44
+
45
+ ##
46
+ # Return a stack of contexts. Most recent node is first.
47
+
48
+ attr_reader :context
49
+
50
+ ##
51
+ # A Hash of Sexp types and Regexp.
52
+ #
53
+ # Print a debug message if the Sexp type matches the Hash key
54
+ # and the Sexp's #inspect output matches the Regexp.
55
+
56
+ attr_accessor :debug
57
+
58
+ ##
59
+ # A default method to call if a process_<type> method is not found
60
+ # for the Sexp type.
61
+
62
+ attr_accessor :default_method
63
+
64
+ ##
65
+ # Expected result class
66
+
67
+ attr_accessor :expected
68
+
69
+ ##
70
+ # Raise an exception if the Sexp is not empty after processing
71
+
72
+ attr_accessor :require_empty
73
+
74
+ ##
75
+ # Raise an exception if no process_<type> method is found for a Sexp.
76
+
77
+ attr_accessor :strict
78
+
79
+ ##
80
+ # An array that specifies node types that are unsupported by this
81
+ # processor. SexpProcessor will raise UnsupportedNodeError if you try
82
+ # to process one of those node types.
83
+
84
+ attr_accessor :unsupported
85
+
86
+ ##
87
+ # Emit a warning when the method in #default_method is called.
88
+
89
+ attr_accessor :warn_on_default
90
+
91
+ ##
92
+ # A scoped environment to make you happy.
93
+
94
+ attr_reader :env
95
+
96
+ ##
97
+ # Creates a new SexpProcessor. Use super to invoke this
98
+ # initializer from SexpProcessor subclasses, then use the
99
+ # attributes above to customize the functionality of the
100
+ # SexpProcessor
101
+
102
+ def initialize
103
+ @default_method = nil
104
+ @warn_on_default = true
105
+ @auto_shift_type = false
106
+ @strict = false
107
+ @unsupported = [:alloca, :cfunc, :cref, :ifunc, :last, :memo,
108
+ :newline, :opt_n, :method]
109
+ @unsupported_checked = false
110
+ @debug = {}
111
+ @expected = Sexp
112
+ @require_empty = true
113
+ @exceptions = {}
114
+
115
+ # we do this on an instance basis so we can subclass it for
116
+ # different processors.
117
+ @processors = {}
118
+ @rewriters = {}
119
+ @context = []
120
+
121
+ public_methods.each do |name|
122
+ case name
123
+ when /^process_(.*)/ then
124
+ @processors[$1.intern] = name.intern
125
+ when /^rewrite_(.*)/ then
126
+ @rewriters[$1.intern] = name.intern
127
+ end
128
+ end
129
+ end
130
+
131
+ def assert_empty(meth, exp, exp_orig)
132
+ unless exp.empty? then
133
+ msg = "exp not empty after #{self.class}.#{meth} on #{exp.inspect}"
134
+ msg += " from #{exp_orig.inspect}" if $DEBUG
135
+ raise NotEmptyError, msg
136
+ end
137
+ end
138
+
139
+ def rewrite(exp)
140
+ type = exp.first
141
+
142
+ self.context.unshift type
143
+
144
+ exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
145
+
146
+ self.context.shift
147
+
148
+ begin
149
+ meth = @rewriters[type]
150
+ exp = self.send(meth, exp) if meth
151
+ break unless Sexp === exp
152
+ old_type, type = type, exp.first
153
+ end until old_type == type
154
+
155
+ exp
156
+ end
157
+
158
+ ##
159
+ # Default Sexp processor. Invokes process_<type> methods matching
160
+ # the Sexp type given. Performs additional checks as specified by
161
+ # the initializer.
162
+
163
+ def process(exp)
164
+ return nil if exp.nil?
165
+ exp = self.rewrite(exp) if self.context.empty?
166
+
167
+ unless @unsupported_checked then
168
+ m = public_methods.grep(/^process_/) { |o| o.to_s.sub(/^process_/, '').intern }
169
+ supported = m - (m - @unsupported)
170
+
171
+ raise UnsupportedNodeError, "#{supported.inspect} shouldn't be in @unsupported" unless supported.empty?
172
+
173
+ @unsupported_checked = true
174
+ end
175
+
176
+ result = self.expected.new
177
+
178
+ type = exp.first
179
+ raise "type should be a Symbol, not: #{exp.first.inspect}" unless
180
+ Symbol === type
181
+
182
+ self.context.unshift type
183
+
184
+ if @debug.has_key? type then
185
+ str = exp.inspect
186
+ puts "// DEBUG: #{str}" if str =~ @debug[type]
187
+ end
188
+
189
+ exp_orig = nil
190
+ exp_orig = exp.deep_clone if $DEBUG or
191
+ @debug.has_key? type or @exceptions.has_key?(type)
192
+
193
+ raise UnsupportedNodeError, "'#{type}' is not a supported node type" if
194
+ @unsupported.include? type
195
+
196
+ if @debug.has_key? type then
197
+ str = exp.inspect
198
+ puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
199
+ end
200
+
201
+ # now do a pass with the real processor (or generic)
202
+ meth = @processors[type] || @default_method
203
+ if meth then
204
+
205
+ if @warn_on_default and meth == @default_method then
206
+ warn "WARNING: Using default method #{meth} for #{type}"
207
+ end
208
+
209
+ exp.shift if @auto_shift_type and meth != @default_method
210
+
211
+ result = error_handler(type, exp_orig) do
212
+ self.send(meth, exp)
213
+ end
214
+
215
+ raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
216
+
217
+ self.assert_empty(meth, exp, exp_orig) if @require_empty
218
+ else
219
+ unless @strict then
220
+ until exp.empty? do
221
+ sub_exp = exp.shift
222
+ sub_result = nil
223
+ if Array === sub_exp then
224
+ sub_result = error_handler(type, exp_orig) do
225
+ process(sub_exp)
226
+ end
227
+ raise "Result is a bad type" unless Array === sub_exp
228
+ raise "Result does not have a type in front: #{sub_exp.inspect}" unless Symbol === sub_exp.first unless sub_exp.empty?
229
+ else
230
+ sub_result = sub_exp
231
+ end
232
+ result << sub_result
233
+ end
234
+
235
+ # NOTE: this is costly, but we are in the generic processor
236
+ # so we shouldn't hit it too much with RubyToC stuff at least.
237
+ #if Sexp === exp and not exp.sexp_type.nil? then
238
+ begin
239
+ result.sexp_type = exp.sexp_type
240
+ rescue Exception
241
+ # nothing to do, on purpose
242
+ end
243
+ else
244
+ msg = "Bug! Unknown node-type #{type.inspect} to #{self.class}"
245
+ msg += " in #{exp_orig.inspect} from #{caller.inspect}" if $DEBUG
246
+ raise UnknownNodeError, msg
247
+ end
248
+ end
249
+
250
+ self.context.shift
251
+ result
252
+ end
253
+
254
+ ##
255
+ # Raises unless the Sexp type for +list+ matches +typ+
256
+
257
+ def assert_type(list, typ)
258
+ raise SexpTypeError, "Expected type #{typ.inspect} in #{list.inspect}" if
259
+ not Array === list or list.first != typ
260
+ end
261
+
262
+ def error_handler(type, exp=nil) # :nodoc:
263
+ begin
264
+ return yield
265
+ rescue StandardError => err
266
+ if @exceptions.has_key? type then
267
+ return @exceptions[type].call(self, exp, err)
268
+ else
269
+ warn "#{err.class} Exception thrown while processing #{type} for sexp #{exp.inspect} #{caller.inspect}" if $DEBUG
270
+ raise
271
+ end
272
+ end
273
+ end
274
+
275
+ ##
276
+ # Registers an error handler for +node+
277
+
278
+ def on_error_in(node_type, &block)
279
+ @exceptions[node_type] = block
280
+ end
281
+
282
+ ##
283
+ # A fairly generic processor for a dummy node. Dummy nodes are used
284
+ # when your processor is doing a complicated rewrite that replaces
285
+ # the current sexp with multiple sexps.
286
+ #
287
+ # Bogus Example:
288
+ #
289
+ # def process_something(exp)
290
+ # return s(:dummy, process(exp), s(:extra, 42))
291
+ # end
292
+
293
+ def process_dummy(exp)
294
+ result = @expected.new(:dummy) rescue @expected.new
295
+
296
+ until exp.empty? do
297
+ result << self.process(exp.shift)
298
+ end
299
+
300
+ result
301
+ end
302
+
303
+ ##
304
+ # Add a scope level to the current env. Eg:
305
+ #
306
+ # def process_defn exp
307
+ # name = exp.shift
308
+ # args = process(exp.shift)
309
+ # scope do
310
+ # body = process(exp.shift)
311
+ # # ...
312
+ # end
313
+ # end
314
+ #
315
+ # env[:x] = 42
316
+ # scope do
317
+ # env[:x] # => 42
318
+ # env[:y] = 24
319
+ # end
320
+ # env[:y] # => nil
321
+
322
+ def scope &block
323
+ env.scope(&block)
324
+ end
325
+
326
+ ##
327
+ # I really hate this here, but I hate subdirs in my lib dir more...
328
+ # I guess it is kinda like shaving... I'll split this out when it
329
+ # itches too much...
330
+
331
+ class Environment
332
+ def initialize
333
+ @env = []
334
+ @env.unshift({})
335
+ end
336
+
337
+ def all
338
+ @env.reverse.inject { |env, scope| env.merge scope }
339
+ end
340
+
341
+ def depth
342
+ @env.length
343
+ end
344
+
345
+ # TODO: depth_of
346
+
347
+ def [] name
348
+ hash = @env.find { |closure| closure.has_key? name }
349
+ hash[name] if hash
350
+ end
351
+
352
+ def []= name, val
353
+ hash = @env.find { |closure| closure.has_key? name } || @env.first
354
+ hash[name] = val
355
+ end
356
+
357
+ def scope
358
+ @env.unshift({})
359
+ begin
360
+ yield
361
+ ensure
362
+ @env.shift
363
+ raise "You went too far unextending env" if @env.empty?
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ class Object
370
+
371
+ ##
372
+ # deep_clone is the usual Marshalling hack to make a deep copy.
373
+ # It is rather slow, so use it sparingly. Helps with debugging
374
+ # SexpProcessors since you usually shift off sexps.
375
+
376
+ def deep_clone
377
+ Marshal.load(Marshal.dump(self))
378
+ end
379
+ end
380
+
381
+ ##
382
+ # SexpProcessor base exception class.
383
+
384
+ class SexpProcessorError < StandardError; end
385
+
386
+ ##
387
+ # Raised by SexpProcessor if it sees a node type listed in its
388
+ # unsupported list.
389
+
390
+ class UnsupportedNodeError < SexpProcessorError; end
391
+
392
+ ##
393
+ # Raised by SexpProcessor if it is in strict mode and sees a node for
394
+ # which there is no processor available.
395
+
396
+ class UnknownNodeError < SexpProcessorError; end
397
+
398
+ ##
399
+ # Raised by SexpProcessor if a processor did not process every node in
400
+ # a sexp and @require_empty is true.
401
+
402
+ class NotEmptyError < SexpProcessorError; end
403
+
404
+ ##
405
+ # Raised if assert_type encounters an unexpected sexp type.
406
+
407
+ class SexpTypeError < SexpProcessorError; end