blob: e945deb5ce9425d751f69985a15236828435c3e5 [file] [log] [blame]
#!/usr/bin/ruby
# encoding: utf-8
=begin LICENSE
[The "BSD licence"]
Copyright (c) 2009-2010 Kyle Yetter
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=end
module ANTLR3
=begin rdoc ANTLR3::TokenRewriteStream
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.
=end
class TokenRewriteStream < CommonTokenStream
unless defined?( RewriteOperation )
RewriteOperation = Struct.new( :stream, :location, :text )
end
=begin rdoc ANTLR3::TokenRewriteStream::RewriteOperation
RewiteOperation objects represent some particular editing command that should
be executed by a token rewrite stream at some time in future when the stream is
rendering a rewritten stream.
To perform token stream rewrites safely and efficiently, the rewrites are
executed lazily (that is, only when the rewritten text is explicitly requested).
Rewrite streams implement lazy rewriting by storing the parameters of
edit-inducing methods like +delete+ and +insert+ as RewriteOperation objects in
a rewrite program list.
The three subclasses of RewriteOperation, InsertBefore, Delete, and Replace,
define specific implementations of stream edits.
=end
class RewriteOperation
extend ClassMacros
@operation_name = ''
class << self
##
# the printable name of operations represented by the class -- used for inspection
attr_reader :operation_name
end
##
# :method: execute( buffer )
# run the rewrite operation represented by this object and append the output to +buffer+
abstract :execute
##
# return the name of this operation as set by its class
def name
self.class.operation_name
end
##
# return a compact, readable representation of this operation
def inspect
return "(%s @ %p : %p)" % [ name, location, text ]
end
end
=begin rdoc ANTLR3::TokenRewriteStream::InsertBefore
Represents rewrite operation:
add string <tt>op.text</tt> to the rewrite output immediately before adding the
text content of the token at index <tt>op.index</tt>
=end
class InsertBefore < RewriteOperation
@operation_name = 'insert-before'.freeze
alias index location
alias index= location=
def execute( buffer )
buffer << text.to_s
token = stream[ location ]
buffer << token.text.to_s if token
return location + 1
end
end
=begin rdoc ANTLR3::TokenRewriteStream::Replace
Represents rewrite operation:
add text <tt>op.text</tt> to the rewrite buffer in lieu of the text of tokens
indexed within the range <tt>op.index .. op.last_index</tt>
=end
class Replace < RewriteOperation
@operation_name = 'replace'.freeze
def initialize( stream, location, text )
super( stream, nil, text )
self.location = location
end
def location=( val )
case val
when Range then super( val )
else
val = val.to_i
super( val..val )
end
end
def execute( buffer )
buffer << text.to_s unless text.nil?
return( location.end + 1 )
end
def index
location.first
end
end
=begin rdoc ANTLR3::TokenRewriteStream::Delete
Represents rewrite operation:
skip over the tokens indexed within the range <tt>op.index .. op.last_index</tt>
and do not add any text to the rewrite buffer
=end
class Delete < Replace
@operation_name = 'delete'.freeze
def initialize( stream, location )
super( stream, location, nil )
end
end
class RewriteProgram
def initialize( stream, name = nil )
@stream = stream
@name = name
@operations = []
end
def replace( *range_arguments )
range, text = cast_range( range_arguments, 1 )
op = Replace.new( @stream, range, text )
@operations << op
return op
end
def insert_before( index, text )
index = index.to_i
index < 0 and index += @stream.length
op = InsertBefore.new( @stream, index, text )
@operations << op
return op
end
def insert_after( index, text )
index = index.to_i
index < 0 and index += @stream.length
op = InsertBefore.new( @stream, index + 1, text )
@operations << op
return op
end
def delete( *range_arguments )
range, = cast_range( range_arguments )
op = Delete.new( @stream, range )
@operations << op
return op
end
def reduce
operations = @operations.reverse
reduced = []
until operations.empty?
operation = operations.shift
location = operation.location
case operation
when Replace
operations.delete_if do |prior_operation|
prior_location = prior_operation.location
case prior_operation
when InsertBefore
location.include?( prior_location )
when Replace
if location.covers?( prior_location )
true
elsif location.overlaps?( prior_location )
conflict!( operation, prior_operation )
end
end
end
when InsertBefore
operations.delete_if do |prior_operation|
prior_location = prior_operation.location
case prior_operation
when InsertBefore
if prior_location == location
operation.text += prior_operation.text
true
end
when Replace
if location == prior_location.first
prior_operation.text = operation.text << prior_operation.text.to_s
operation = nil
break( false )
elsif prior_location.include?( location )
conflict!( operation, prior_operation )
end
end
end
end
reduced.unshift( operation ) if operation
end
@operations.replace( reduced )
@operations.inject( {} ) do |map, operation|
other_operaiton = map[ operation.index ] and
ANTLR3.bug!( Util.tidy( <<-END ) % [ self.class, operation, other_operaiton ] )
| %s#reduce! should have left only one operation per index,
| but %p conflicts with %p
END
map[ operation.index ] = operation
map
end
end
def execute( *range_arguments )
if range_arguments.empty?
range = 0 ... @stream.length
else
range, = cast_range( range_arguments )
end
output = ''
tokens = @stream.tokens
operations = reduce
cursor = range.first
while range.include?( cursor )
if operation = operations.delete( cursor )
cursor = operation.execute( output )
else
token = tokens[ cursor ]
output << token.text if token
cursor += 1
end
end
if operation = operations.delete( cursor ) and
operation.is_a?( InsertBefore )
# catch edge 'insert-after' operations
operation.execute( output )
end
return output
end
def clear
@operations.clear
end
def undo( number_of_operations = 1 )
@operations.pop( number_of_operations )
end
def conflict!( current, previous )
message = 'operation %p overlaps with previous operation %p' % [ current, previous ]
raise( RangeError, message, caller )
end
def cast_range( args, extra = 0 )
single, pair = extra + 1, extra + 2
case check_arguments( args, single, pair )
when single
loc = args.shift
if loc.is_a?( Range )
first, last = loc.first.to_i, loc.last.to_i
loc.exclude_end? and last -= 1
return cast_range( args.unshift( first, last ), extra )
else
loc = loc.to_i
return cast_range( args.unshift( loc, loc ), extra )
end
when pair
first, last = args.shift( 2 ).map! { |arg| arg.to_i }
if first < 0 and last < 0
first += @stream.length
last += @stream.length
else
last < 0 and last += @stream.length
first = first.at_least( 0 )
end
return( args.unshift( first .. last ) )
end
end
def check_arguments( args, min, max )
n = args.length
if n < min
raise ArgumentError,
"wrong number of arguments (#{ args.length } for #{ min })",
caller
elsif n > max
raise ArgumentError,
"wrong number of arguments (#{ args.length } for #{ max })",
caller
else return n
end
end
private :conflict!, :cast_range, :check_arguments
end
attr_reader :programs
def initialize( token_source, options = {} )
super( token_source, options )
@programs = Hash.new do |programs, name|
if name.is_a?( String )
programs[ name ] = RewriteProgram.new( self, name )
else programs[ name.to_s ]
end
end
@last_rewrite_token_indexes = {}
end
def rewrite( program_name = 'default', range = nil )
program = @programs[ program_name ]
if block_given?
yield( program )
program.execute( range )
else program
end
end
def program( name = 'default' )
return @programs[ name ]
end
def delete_program( name = 'default' )
@programs.delete( name )
end
def original_string( start = 0, finish = size - 1 )
@position == -1 and fill_buffer
return( self[ start..finish ].map { |t| t.text }.join( '' ) )
end
def insert_before( *args )
@programs[ 'default' ].insert_before( *args )
end
def insert_after( *args )
@programs[ 'default' ].insert_after( *args )
end
def replace( *args )
@programs[ 'default' ].replace( *args )
end
def delete( *args )
@programs[ 'default' ].delete( *args )
end
def render( *arguments )
case arguments.first
when String, Symbol then name = arguments.shift.to_s
else name = 'default'
end
@programs[ name ].execute( *arguments )
end
end
end