sexp_processor 3.0.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/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