Home | History | Annotate | Download | only in streams
      1 #!/usr/bin/ruby
      2 # encoding: utf-8
      3 
      4 =begin LICENSE
      5 
      6 [The "BSD licence"]
      7 Copyright (c) 2009-2010 Kyle Yetter
      8 All rights reserved.
      9 
     10 Redistribution and use in source and binary forms, with or without
     11 modification, are permitted provided that the following conditions
     12 are 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 
     22 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     23 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     24 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     25 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     27 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     28 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     29 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     31 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     32 
     33 =end
     34 
     35 module ANTLR3
     36 
     37 =begin rdoc ANTLR3::TokenRewriteStream
     38 
     39 TokenRewriteStream 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 
     43 class TokenRewriteStream < CommonTokenStream
     44 
     45   unless defined?( RewriteOperation )
     46     RewriteOperation = Struct.new( :stream, :location, :text )
     47   end
     48 
     49 =begin rdoc ANTLR3::TokenRewriteStream::RewriteOperation
     50 
     51 RewiteOperation objects represent some particular editing command that should
     52 be executed by a token rewrite stream at some time in future when the stream is
     53 rendering a rewritten stream.
     54 
     55 To perform token stream rewrites safely and efficiently, the rewrites are
     56 executed lazily (that is, only when the rewritten text is explicitly requested).
     57 Rewrite streams implement lazy rewriting by storing the parameters of
     58 edit-inducing methods like +delete+ and +insert+ as RewriteOperation objects in
     59 a rewrite program list.
     60 
     61 The three subclasses of RewriteOperation, InsertBefore, Delete, and Replace,
     62 define 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 
     97 Represents rewrite operation:
     98 
     99 add string <tt>op.text</tt> to the rewrite output immediately before adding the
    100 text 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 
    120 Represents rewrite operation:
    121 
    122 add text <tt>op.text</tt> to the rewrite buffer in lieu of the text of tokens
    123 indexed 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 
    158 Represents rewrite operation:
    159 
    160 skip over the tokens indexed within the range <tt>op.index .. op.last_index</tt>
    161 and 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
    423 end
    424 end
    425