1#!/usr/bin/ruby
2# encoding: utf-8
3
4=begin LICENSE
5
6[The "BSD licence"]
7Copyright (c) 2009-2010 Kyle Yetter
8All rights reserved.
9
10Redistribution and use in source and binary forms, with or without
11modification, are permitted provided that the following conditions
12are met:
13
14 1. Redistributions of source code must retain the above copyright
15    notice, this list of conditions and the following disclaimer.
16 2. Redistributions in binary form must reproduce the above copyright
17    notice, this list of conditions and the following disclaimer in the
18    documentation and/or other materials provided with the distribution.
19 3. The name of the author may not be used to endorse or promote products
20    derived from this software without specific prior written permission.
21
22THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33=end
34
35module ANTLR3
36
37=begin rdoc ANTLR3::TokenRewriteStream
38
39TokenRewriteStream is a specialized form of CommonTokenStream that provides simple stream editing functionality. By creating <i>rewrite programs</i>, new text output can be created based upon the tokens in the stream. The basic token stream itself is preserved, and text output is rendered on demand using the #to_s method.
40
41=end
42
43class TokenRewriteStream < CommonTokenStream
44
45  unless defined?( RewriteOperation )
46    RewriteOperation = Struct.new( :stream, :location, :text )
47  end
48
49=begin rdoc ANTLR3::TokenRewriteStream::RewriteOperation
50
51RewiteOperation objects represent some particular editing command that should
52be executed by a token rewrite stream at some time in future when the stream is
53rendering a rewritten stream.
54
55To perform token stream rewrites safely and efficiently, the rewrites are
56executed lazily (that is, only when the rewritten text is explicitly requested).
57Rewrite streams implement lazy rewriting by storing the parameters of
58edit-inducing methods like +delete+ and +insert+ as RewriteOperation objects in
59a rewrite program list.
60
61The three subclasses of RewriteOperation, InsertBefore, Delete, and Replace,
62define specific implementations of stream edits.
63
64=end
65
66  class RewriteOperation
67    extend ClassMacros
68    @operation_name = ''
69
70    class << self
71      ##
72      # the printable name of operations represented by the class -- used for inspection
73      attr_reader :operation_name
74    end
75
76    ##
77    # :method: execute( buffer )
78    # run the rewrite operation represented by this object and append the output to +buffer+
79    abstract :execute
80
81    ##
82    # return the name of this operation as set by its class
83    def name
84      self.class.operation_name
85    end
86
87    ##
88    # return a compact, readable representation of this operation
89    def inspect
90      return "(%s @ %p : %p)" % [ name, location, text ]
91    end
92  end
93
94
95=begin rdoc ANTLR3::TokenRewriteStream::InsertBefore
96
97Represents rewrite operation:
98
99add string <tt>op.text</tt> to the rewrite output immediately before adding the
100text content of the token at index <tt>op.index</tt>
101
102=end
103
104  class InsertBefore < RewriteOperation
105    @operation_name = 'insert-before'.freeze
106
107    alias index  location
108    alias index= location=
109
110    def execute( buffer )
111      buffer << text.to_s
112      token = stream[ location ]
113      buffer << token.text.to_s if token
114      return location + 1
115    end
116  end
117
118=begin rdoc ANTLR3::TokenRewriteStream::Replace
119
120Represents rewrite operation:
121
122add text <tt>op.text</tt> to the rewrite buffer in lieu of the text of tokens
123indexed within the range <tt>op.index .. op.last_index</tt>
124
125=end
126
127  class Replace < RewriteOperation
128
129    @operation_name = 'replace'.freeze
130
131    def initialize( stream, location, text )
132      super( stream, nil, text )
133      self.location = location
134    end
135
136    def location=( val )
137      case val
138      when Range then super( val )
139      else
140        val = val.to_i
141        super( val..val )
142      end
143    end
144
145    def execute( buffer )
146      buffer << text.to_s unless text.nil?
147      return( location.end + 1 )
148    end
149
150    def index
151      location.first
152    end
153
154  end
155
156=begin rdoc ANTLR3::TokenRewriteStream::Delete
157
158Represents rewrite operation:
159
160skip over the tokens indexed within the range <tt>op.index .. op.last_index</tt>
161and do not add any text to the rewrite buffer
162
163=end
164
165  class Delete < Replace
166    @operation_name = 'delete'.freeze
167
168    def initialize( stream, location )
169      super( stream, location, nil )
170    end
171  end
172
173  class RewriteProgram
174    def initialize( stream, name = nil )
175      @stream = stream
176      @name = name
177      @operations = []
178    end
179
180    def replace( *range_arguments )
181      range, text = cast_range( range_arguments, 1 )
182
183      op = Replace.new( @stream, range, text )
184      @operations << op
185      return op
186    end
187
188    def insert_before( index, text )
189      index = index.to_i
190      index < 0 and index += @stream.length
191      op = InsertBefore.new( @stream, index, text )
192      @operations << op
193      return op
194    end
195
196    def insert_after( index, text )
197      index = index.to_i
198      index < 0 and index += @stream.length
199      op = InsertBefore.new( @stream, index + 1, text )
200      @operations << op
201      return op
202    end
203
204    def delete( *range_arguments )
205      range, = cast_range( range_arguments )
206      op = Delete.new( @stream, range )
207      @operations << op
208      return op
209    end
210
211    def reduce
212      operations = @operations.reverse
213      reduced = []
214
215      until operations.empty?
216        operation = operations.shift
217        location = operation.location
218
219        case operation
220        when Replace
221          operations.delete_if do |prior_operation|
222            prior_location = prior_operation.location
223
224            case prior_operation
225            when InsertBefore
226              location.include?( prior_location )
227            when Replace
228              if location.covers?( prior_location )
229                true
230              elsif location.overlaps?( prior_location )
231                conflict!( operation, prior_operation )
232              end
233            end
234          end
235        when InsertBefore
236          operations.delete_if do |prior_operation|
237            prior_location = prior_operation.location
238
239            case prior_operation
240            when InsertBefore
241              if prior_location == location
242                operation.text += prior_operation.text
243                true
244              end
245            when Replace
246              if location == prior_location.first
247                prior_operation.text = operation.text << prior_operation.text.to_s
248                operation = nil
249                break( false )
250              elsif prior_location.include?( location )
251                conflict!( operation, prior_operation )
252              end
253            end
254          end
255        end
256
257        reduced.unshift( operation ) if operation
258      end
259
260      @operations.replace( reduced )
261
262      @operations.inject( {} ) do |map, operation|
263        other_operaiton = map[ operation.index ] and
264          ANTLR3.bug!( Util.tidy( <<-END ) % [ self.class, operation, other_operaiton ] )
265          | %s#reduce! should have left only one operation per index,
266          | but %p conflicts with %p
267          END
268        map[ operation.index ] = operation
269        map
270      end
271    end
272
273    def execute( *range_arguments )
274      if range_arguments.empty?
275        range = 0 ... @stream.length
276      else
277        range, = cast_range( range_arguments )
278      end
279
280      output = ''
281
282      tokens = @stream.tokens
283
284      operations = reduce
285
286      cursor = range.first
287      while range.include?( cursor )
288        if operation = operations.delete( cursor )
289          cursor = operation.execute( output )
290        else
291          token = tokens[ cursor ]
292          output << token.text if token
293          cursor += 1
294        end
295      end
296      if operation = operations.delete( cursor ) and
297         operation.is_a?( InsertBefore )
298        # catch edge 'insert-after' operations
299        operation.execute( output )
300      end
301
302      return output
303    end
304
305    def clear
306      @operations.clear
307    end
308
309    def undo( number_of_operations = 1 )
310      @operations.pop( number_of_operations )
311    end
312
313    def conflict!( current, previous )
314      message = 'operation %p overlaps with previous operation %p' % [ current, previous ]
315      raise( RangeError, message, caller )
316    end
317
318    def cast_range( args, extra = 0 )
319      single, pair = extra + 1, extra + 2
320      case check_arguments( args, single, pair )
321      when single
322        loc = args.shift
323
324        if loc.is_a?( Range )
325          first, last = loc.first.to_i, loc.last.to_i
326          loc.exclude_end? and last -= 1
327          return cast_range( args.unshift( first, last ), extra )
328        else
329          loc = loc.to_i
330          return cast_range( args.unshift( loc, loc ), extra )
331        end
332      when pair
333        first, last = args.shift( 2 ).map! { |arg| arg.to_i }
334        if first < 0 and last < 0
335          first += @stream.length
336          last += @stream.length
337        else
338          last < 0 and last += @stream.length
339          first = first.at_least( 0 )
340        end
341        return( args.unshift( first .. last ) )
342      end
343    end
344
345    def check_arguments( args, min, max )
346      n = args.length
347      if n < min
348        raise ArgumentError,
349          "wrong number of arguments (#{ args.length } for #{ min })",
350          caller
351      elsif n > max
352        raise ArgumentError,
353          "wrong number of arguments (#{ args.length } for #{ max })",
354          caller
355      else return n
356      end
357    end
358
359    private :conflict!, :cast_range, :check_arguments
360  end
361
362  attr_reader :programs
363
364  def initialize( token_source, options = {} )
365    super( token_source, options )
366
367    @programs = Hash.new do |programs, name|
368      if name.is_a?( String )
369        programs[ name ] = RewriteProgram.new( self, name )
370      else programs[ name.to_s ]
371      end
372    end
373
374    @last_rewrite_token_indexes = {}
375  end
376
377  def rewrite( program_name = 'default', range = nil )
378    program = @programs[ program_name ]
379    if block_given?
380      yield( program )
381      program.execute( range )
382    else program
383    end
384  end
385
386  def program( name = 'default' )
387    return @programs[ name ]
388  end
389
390  def delete_program( name = 'default' )
391    @programs.delete( name )
392  end
393
394  def original_string( start = 0, finish = size - 1 )
395    @position == -1 and fill_buffer
396
397    return( self[ start..finish ].map { |t| t.text }.join( '' ) )
398  end
399
400  def insert_before( *args )
401    @programs[ 'default' ].insert_before( *args )
402  end
403
404  def insert_after( *args )
405    @programs[ 'default' ].insert_after( *args )
406  end
407
408  def replace( *args )
409    @programs[ 'default' ].replace( *args )
410  end
411
412  def delete( *args )
413    @programs[ 'default' ].delete( *args )
414  end
415
416  def render( *arguments )
417    case arguments.first
418    when String, Symbol then name = arguments.shift.to_s
419    else name = 'default'
420    end
421    @programs[ name ].execute( *arguments )
422  end
423end
424end
425