blob: e9ab4a9336a57075cd91f9d071dd2ca4f8cb2308 [file] [log] [blame]
/*
* [The "BSD licence"]
* Copyright (c) 2005-2008 Terence Parr
* All rights reserved.
*
* Conversion to C#:
* Copyright (c) 2008-2009 Sam Harwell, Pixel Mine, Inc.
* 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.
*/
namespace Antlr.Runtime
{
using System.Collections.Generic;
using ArgumentException = System.ArgumentException;
using Console = System.Console;
using Math = System.Math;
using DebuggerDisplay = System.Diagnostics.DebuggerDisplayAttribute;
using Exception = System.Exception;
using StringBuilder = System.Text.StringBuilder;
using Type = System.Type;
/** Useful for dumping out the input stream after doing some
* augmentation or other manipulations.
*
* You can insert stuff, replace, and delete chunks. Note that the
* operations are done lazily--only if you convert the buffer to a
* String. This is very efficient because you are not moving data around
* all the time. As the buffer of tokens is converted to strings, the
* toString() method(s) check to see if there is an operation at the
* current index. If so, the operation is done and then normal String
* rendering continues on the buffer. This is like having multiple Turing
* machine instruction streams (programs) operating on a single input tape. :)
*
* Since the operations are done lazily at toString-time, operations do not
* screw up the token index values. That is, an insert operation at token
* index i does not change the index values for tokens i+1..n-1.
*
* Because operations never actually alter the buffer, you may always get
* the original token stream back without undoing anything. Since
* the instructions are queued up, you can easily simulate transactions and
* roll back any changes if there is an error just by removing instructions.
* For example,
*
* CharStream input = new ANTLRFileStream("input");
* TLexer lex = new TLexer(input);
* TokenRewriteStream tokens = new TokenRewriteStream(lex);
* T parser = new T(tokens);
* parser.startRule();
*
* Then in the rules, you can execute
* Token t,u;
* ...
* input.insertAfter(t, "text to put after t");}
* input.insertAfter(u, "text after u");}
* System.out.println(tokens.toString());
*
* Actually, you have to cast the 'input' to a TokenRewriteStream. :(
*
* You can also have multiple "instruction streams" and get multiple
* rewrites from a single pass over the input. Just name the instruction
* streams and use that name again when printing the buffer. This could be
* useful for generating a C file and also its header file--all from the
* same buffer:
*
* tokens.insertAfter("pass1", t, "text to put after t");}
* tokens.insertAfter("pass2", u, "text after u");}
* System.out.println(tokens.toString("pass1"));
* System.out.println(tokens.toString("pass2"));
*
* If you don't use named rewrite streams, a "default" stream is used as
* the first example shows.
*/
[System.Serializable]
[DebuggerDisplay( "TODO: TokenRewriteStream debugger display" )]
public class TokenRewriteStream : CommonTokenStream
{
public const string DEFAULT_PROGRAM_NAME = "default";
public const int PROGRAM_INIT_SIZE = 100;
public const int MIN_TOKEN_INDEX = 0;
// Define the rewrite operation hierarchy
protected class RewriteOperation
{
/** <summary>What index into rewrites List are we?</summary> */
public int instructionIndex;
/** <summary>Token buffer index.</summary> */
public int index;
public object text;
// outer
protected TokenRewriteStream stream;
protected RewriteOperation(TokenRewriteStream stream, int index)
{
this.stream = stream;
this.index = index;
}
protected RewriteOperation( TokenRewriteStream stream, int index, object text )
{
this.index = index;
this.text = text;
this.stream = stream;
}
/** <summary>
* Execute the rewrite operation by possibly adding to the buffer.
* Return the index of the next token to operate on.
* </summary>
*/
public virtual int Execute( StringBuilder buf )
{
return index;
}
public override string ToString()
{
string opName = this.GetType().Name;
int dindex = opName.IndexOf( '$' );
opName = opName.Substring( dindex + 1 );
return string.Format("<{0}@{1}:\"{2}\">", opName, stream._tokens[index], text);
}
}
private class InsertBeforeOp : RewriteOperation
{
public InsertBeforeOp( TokenRewriteStream stream, int index, object text ) :
base( stream, index, text )
{
}
public override int Execute( StringBuilder buf )
{
buf.Append( text );
if (stream._tokens[index].Type != CharStreamConstants.EndOfFile)
buf.Append(stream._tokens[index].Text);
return index + 1;
}
}
/** <summary>
* I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp
* instructions.
* </summary>
*/
private class ReplaceOp : RewriteOperation
{
public int lastIndex;
public ReplaceOp( TokenRewriteStream stream, int from, int to, object text )
: base( stream, from, text )
{
lastIndex = to;
}
public override int Execute( StringBuilder buf )
{
if ( text != null )
{
buf.Append( text );
}
return lastIndex + 1;
}
public override string ToString()
{
if (text == null)
{
return string.Format("<DeleteOp@{0}..{1}>", stream._tokens[index], stream._tokens[lastIndex]);
}
return string.Format("<ReplaceOp@{0}..{1}:\"{2}\">", stream._tokens[index], stream._tokens[lastIndex], text);
}
}
/** <summary>
* You may have multiple, named streams of rewrite operations.
* I'm calling these things "programs."
* Maps String (name) -> rewrite (List)
* </summary>
*/
protected IDictionary<string, IList<RewriteOperation>> programs = null;
/** <summary>Map String (program name) -> Integer index</summary> */
protected IDictionary<string, int> lastRewriteTokenIndexes = null;
public TokenRewriteStream()
{
Init();
}
protected void Init()
{
programs = new Dictionary<string, IList<RewriteOperation>>();
programs[DEFAULT_PROGRAM_NAME] = new List<RewriteOperation>( PROGRAM_INIT_SIZE );
lastRewriteTokenIndexes = new Dictionary<string, int>();
}
public TokenRewriteStream( ITokenSource tokenSource )
: base( tokenSource )
{
Init();
}
public TokenRewriteStream( ITokenSource tokenSource, int channel )
: base( tokenSource, channel )
{
Init();
}
public virtual void Rollback( int instructionIndex )
{
Rollback( DEFAULT_PROGRAM_NAME, instructionIndex );
}
/** <summary>
* Rollback the instruction stream for a program so that
* the indicated instruction (via instructionIndex) is no
* longer in the stream. UNTESTED!
* </summary>
*/
public virtual void Rollback( string programName, int instructionIndex )
{
IList<RewriteOperation> @is;
if ( programs.TryGetValue( programName, out @is ) && @is != null )
{
List<RewriteOperation> sublist = new List<RewriteOperation>();
for ( int i = MIN_TOKEN_INDEX; i <= instructionIndex; i++ )
sublist.Add( @is[i] );
programs[programName] = sublist;
}
}
public virtual void DeleteProgram()
{
DeleteProgram( DEFAULT_PROGRAM_NAME );
}
/** <summary>Reset the program so that no instructions exist</summary> */
public virtual void DeleteProgram( string programName )
{
Rollback( programName, MIN_TOKEN_INDEX );
}
public virtual void InsertAfter( IToken t, object text )
{
InsertAfter( DEFAULT_PROGRAM_NAME, t, text );
}
public virtual void InsertAfter( int index, object text )
{
InsertAfter( DEFAULT_PROGRAM_NAME, index, text );
}
public virtual void InsertAfter( string programName, IToken t, object text )
{
InsertAfter( programName, t.TokenIndex, text );
}
public virtual void InsertAfter( string programName, int index, object text )
{
// to insert after, just insert before next index (even if past end)
InsertBefore( programName, index + 1, text );
}
public virtual void InsertBefore( IToken t, object text )
{
InsertBefore( DEFAULT_PROGRAM_NAME, t, text );
}
public virtual void InsertBefore( int index, object text )
{
InsertBefore( DEFAULT_PROGRAM_NAME, index, text );
}
public virtual void InsertBefore( string programName, IToken t, object text )
{
InsertBefore( programName, t.TokenIndex, text );
}
public virtual void InsertBefore( string programName, int index, object text )
{
RewriteOperation op = new InsertBeforeOp( this, index, text );
IList<RewriteOperation> rewrites = GetProgram( programName );
op.instructionIndex = rewrites.Count;
rewrites.Add( op );
}
public virtual void Replace( int index, object text )
{
Replace( DEFAULT_PROGRAM_NAME, index, index, text );
}
public virtual void Replace( int from, int to, object text )
{
Replace( DEFAULT_PROGRAM_NAME, from, to, text );
}
public virtual void Replace( IToken indexT, object text )
{
Replace( DEFAULT_PROGRAM_NAME, indexT, indexT, text );
}
public virtual void Replace( IToken from, IToken to, object text )
{
Replace( DEFAULT_PROGRAM_NAME, from, to, text );
}
public virtual void Replace( string programName, int from, int to, object text )
{
if ( from > to || from < 0 || to < 0 || to >= _tokens.Count )
{
throw new ArgumentException( "replace: range invalid: " + from + ".." + to + "(size=" + _tokens.Count + ")" );
}
RewriteOperation op = new ReplaceOp( this, from, to, text );
IList<RewriteOperation> rewrites = GetProgram( programName );
op.instructionIndex = rewrites.Count;
rewrites.Add( op );
}
public virtual void Replace( string programName, IToken from, IToken to, object text )
{
Replace( programName,
from.TokenIndex,
to.TokenIndex,
text );
}
public virtual void Delete( int index )
{
Delete( DEFAULT_PROGRAM_NAME, index, index );
}
public virtual void Delete( int from, int to )
{
Delete( DEFAULT_PROGRAM_NAME, from, to );
}
public virtual void Delete( IToken indexT )
{
Delete( DEFAULT_PROGRAM_NAME, indexT, indexT );
}
public virtual void Delete( IToken from, IToken to )
{
Delete( DEFAULT_PROGRAM_NAME, from, to );
}
public virtual void Delete( string programName, int from, int to )
{
Replace( programName, from, to, null );
}
public virtual void Delete( string programName, IToken from, IToken to )
{
Replace( programName, from, to, null );
}
public virtual int GetLastRewriteTokenIndex()
{
return GetLastRewriteTokenIndex( DEFAULT_PROGRAM_NAME );
}
protected virtual int GetLastRewriteTokenIndex( string programName )
{
int value;
if ( lastRewriteTokenIndexes.TryGetValue( programName, out value ) )
return value;
return -1;
}
protected virtual void SetLastRewriteTokenIndex( string programName, int i )
{
lastRewriteTokenIndexes[programName] = i;
}
protected virtual IList<RewriteOperation> GetProgram( string name )
{
IList<RewriteOperation> @is;
if ( !programs.TryGetValue( name, out @is ) || @is == null )
{
@is = InitializeProgram( name );
}
return @is;
}
private IList<RewriteOperation> InitializeProgram( string name )
{
IList<RewriteOperation> @is = new List<RewriteOperation>( PROGRAM_INIT_SIZE );
programs[name] = @is;
return @is;
}
public virtual string ToOriginalString()
{
Fill();
return ToOriginalString( MIN_TOKEN_INDEX, Count - 1 );
}
public virtual string ToOriginalString( int start, int end )
{
StringBuilder buf = new StringBuilder();
for ( int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < _tokens.Count; i++ )
{
if (Get(i).Type != CharStreamConstants.EndOfFile)
buf.Append(Get(i).Text);
}
return buf.ToString();
}
public override string ToString()
{
Fill();
return ToString( MIN_TOKEN_INDEX, Count - 1 );
}
public virtual string ToString( string programName )
{
Fill();
return ToString(programName, MIN_TOKEN_INDEX, Count - 1);
}
public override string ToString( int start, int end )
{
return ToString( DEFAULT_PROGRAM_NAME, start, end );
}
public virtual string ToString( string programName, int start, int end )
{
IList<RewriteOperation> rewrites;
if ( !programs.TryGetValue( programName, out rewrites ) )
rewrites = null;
// ensure start/end are in range
if ( end > _tokens.Count - 1 )
end = _tokens.Count - 1;
if ( start < 0 )
start = 0;
if ( rewrites == null || rewrites.Count == 0 )
{
return ToOriginalString( start, end ); // no instructions to execute
}
StringBuilder buf = new StringBuilder();
// First, optimize instruction stream
IDictionary<int, RewriteOperation> indexToOp = ReduceToSingleOperationPerIndex( rewrites );
// Walk buffer, executing instructions and emitting tokens
int i = start;
while ( i <= end && i < _tokens.Count )
{
RewriteOperation op;
bool exists = indexToOp.TryGetValue( i, out op );
if ( exists )
{
// remove so any left have index size-1
indexToOp.Remove( i );
}
if ( !exists || op == null )
{
IToken t = _tokens[i];
// no operation at that index, just dump token
if (t.Type != CharStreamConstants.EndOfFile)
buf.Append(t.Text);
i++; // move to next token
}
else
{
i = op.Execute( buf ); // execute operation and skip
}
}
// include stuff after end if it's last index in buffer
// So, if they did an insertAfter(lastValidIndex, "foo"), include
// foo if end==lastValidIndex.
if ( end == _tokens.Count - 1 )
{
// Scan any remaining operations after last token
// should be included (they will be inserts).
foreach ( RewriteOperation op in indexToOp.Values )
{
if ( op.index >= _tokens.Count - 1 )
buf.Append( op.text );
}
}
return buf.ToString();
}
/** We need to combine operations and report invalid operations (like
* overlapping replaces that are not completed nested). Inserts to
* same index need to be combined etc... Here are the cases:
*
* I.i.u I.j.v leave alone, nonoverlapping
* I.i.u I.i.v combine: Iivu
*
* R.i-j.u R.x-y.v | i-j in x-y delete first R
* R.i-j.u R.i-j.v delete first R
* R.i-j.u R.x-y.v | x-y in i-j ERROR
* R.i-j.u R.x-y.v | boundaries overlap ERROR
*
* Delete special case of replace (text==null):
* D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right)
*
* I.i.u R.x-y.v | i in (x+1)-y delete I (since insert before
* we're not deleting i)
* I.i.u R.x-y.v | i not in (x+1)-y leave alone, nonoverlapping
* R.x-y.v I.i.u | i in x-y ERROR
* R.x-y.v I.x.u R.x-y.uv (combine, delete I)
* R.x-y.v I.i.u | i not in x-y leave alone, nonoverlapping
*
* I.i.u = insert u before op @ index i
* R.x-y.u = replace x-y indexed tokens with u
*
* First we need to examine replaces. For any replace op:
*
* 1. wipe out any insertions before op within that range.
* 2. Drop any replace op before that is contained completely within
* that range.
* 3. Throw exception upon boundary overlap with any previous replace.
*
* Then we can deal with inserts:
*
* 1. for any inserts to same index, combine even if not adjacent.
* 2. for any prior replace with same left boundary, combine this
* insert with replace and delete this replace.
* 3. throw exception if index in same range as previous replace
*
* Don't actually delete; make op null in list. Easier to walk list.
* Later we can throw as we add to index -> op map.
*
* Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
* inserted stuff would be before the replace range. But, if you
* add tokens in front of a method body '{' and then delete the method
* body, I think the stuff before the '{' you added should disappear too.
*
* Return a map from token index to operation.
*/
protected virtual IDictionary<int, RewriteOperation> ReduceToSingleOperationPerIndex( IList<RewriteOperation> rewrites )
{
//System.out.println("rewrites="+rewrites);
// WALK REPLACES
for ( int i = 0; i < rewrites.Count; i++ )
{
RewriteOperation op = rewrites[i];
if ( op == null )
continue;
if ( !( op is ReplaceOp ) )
continue;
ReplaceOp rop = (ReplaceOp)rewrites[i];
// Wipe prior inserts within range
var inserts = GetKindOfOps( rewrites, typeof( InsertBeforeOp ), i );
for ( int j = 0; j < inserts.Count; j++ )
{
InsertBeforeOp iop = (InsertBeforeOp)inserts[j];
if (iop.index == rop.index)
{
// E.g., insert before 2, delete 2..2; update replace
// text to include insert before, kill insert
rewrites[iop.instructionIndex] = null;
rop.text = iop.text.ToString() + (rop.text != null ? rop.text.ToString() : string.Empty);
}
else if (iop.index > rop.index && iop.index <= rop.lastIndex)
{
// delete insert as it's a no-op.
rewrites[iop.instructionIndex] = null;
}
}
// Drop any prior replaces contained within
var prevReplaces = GetKindOfOps( rewrites, typeof( ReplaceOp ), i );
for ( int j = 0; j < prevReplaces.Count; j++ )
{
ReplaceOp prevRop = (ReplaceOp)prevReplaces[j];
if ( prevRop.index >= rop.index && prevRop.lastIndex <= rop.lastIndex )
{
// delete replace as it's a no-op.
rewrites[prevRop.instructionIndex] = null;
continue;
}
// throw exception unless disjoint or identical
bool disjoint =
prevRop.lastIndex < rop.index || prevRop.index > rop.lastIndex;
bool same =
prevRop.index == rop.index && prevRop.lastIndex == rop.lastIndex;
// Delete special case of replace (text==null):
// D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right)
if (prevRop.text == null && rop.text == null && !disjoint)
{
//System.out.println("overlapping deletes: "+prevRop+", "+rop);
rewrites[prevRop.instructionIndex] = null; // kill first delete
rop.index = Math.Min(prevRop.index, rop.index);
rop.lastIndex = Math.Max(prevRop.lastIndex, rop.lastIndex);
Console.WriteLine("new rop " + rop);
}
else if ( !disjoint && !same )
{
throw new ArgumentException( "replace op boundaries of " + rop +
" overlap with previous " + prevRop );
}
}
}
// WALK INSERTS
for ( int i = 0; i < rewrites.Count; i++ )
{
RewriteOperation op = (RewriteOperation)rewrites[i];
if ( op == null )
continue;
if ( !( op is InsertBeforeOp ) )
continue;
InsertBeforeOp iop = (InsertBeforeOp)rewrites[i];
// combine current insert with prior if any at same index
var prevInserts = GetKindOfOps( rewrites, typeof( InsertBeforeOp ), i );
for ( int j = 0; j < prevInserts.Count; j++ )
{
InsertBeforeOp prevIop = (InsertBeforeOp)prevInserts[j];
if ( prevIop.index == iop.index )
{ // combine objects
// convert to strings...we're in process of toString'ing
// whole token buffer so no lazy eval issue with any templates
iop.text = CatOpText( iop.text, prevIop.text );
// delete redundant prior insert
rewrites[prevIop.instructionIndex] = null;
}
}
// look for replaces where iop.index is in range; error
var prevReplaces = GetKindOfOps( rewrites, typeof( ReplaceOp ), i );
for ( int j = 0; j < prevReplaces.Count; j++ )
{
ReplaceOp rop = (ReplaceOp)prevReplaces[j];
if ( iop.index == rop.index )
{
rop.text = CatOpText( iop.text, rop.text );
rewrites[i] = null; // delete current insert
continue;
}
if ( iop.index >= rop.index && iop.index <= rop.lastIndex )
{
throw new ArgumentException( "insert op " + iop +
" within boundaries of previous " + rop );
}
}
}
// System.out.println("rewrites after="+rewrites);
IDictionary<int, RewriteOperation> m = new Dictionary<int, RewriteOperation>();
for ( int i = 0; i < rewrites.Count; i++ )
{
RewriteOperation op = (RewriteOperation)rewrites[i];
if ( op == null )
continue; // ignore deleted ops
RewriteOperation existing;
if ( m.TryGetValue( op.index, out existing ) && existing != null )
{
throw new Exception( "should only be one op per index" );
}
m[op.index] = op;
}
//System.out.println("index to op: "+m);
return m;
}
protected virtual string CatOpText( object a, object b )
{
return string.Concat( a, b );
}
protected virtual IList<RewriteOperation> GetKindOfOps( IList<RewriteOperation> rewrites, Type kind )
{
return GetKindOfOps( rewrites, kind, rewrites.Count );
}
/** <summary>Get all operations before an index of a particular kind</summary> */
protected virtual IList<RewriteOperation> GetKindOfOps( IList<RewriteOperation> rewrites, Type kind, int before )
{
IList<RewriteOperation> ops = new List<RewriteOperation>();
for ( int i = 0; i < before && i < rewrites.Count; i++ )
{
RewriteOperation op = rewrites[i];
if ( op == null )
continue; // ignore deleted
if ( op.GetType() == kind )
ops.Add( op );
}
return ops;
}
public virtual string ToDebugString()
{
return ToDebugString( MIN_TOKEN_INDEX, Count - 1 );
}
public virtual string ToDebugString( int start, int end )
{
StringBuilder buf = new StringBuilder();
for ( int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < _tokens.Count; i++ )
{
buf.Append( Get( i ) );
}
return buf.ToString();
}
}
}