| package org.codehaus.groovy.syntax.lexer; |
| |
| import org.codehaus.groovy.syntax.ReadException; |
| |
| /** |
| * A Lexer for processing here docs. It reads a line at a time from |
| * the underlying stream (leaving the EOL for the next read), then |
| * offers that data for users. |
| * |
| * @author Chris Poirier |
| */ |
| |
| public class HereDocLexer extends TextLexerBase |
| { |
| |
| protected String marker = null; // The marker to watch for |
| protected boolean onmargin = true; // If false, the marker can be indented |
| protected String data = ""; // The current data |
| protected int consumed = -1; // The last index consumed |
| protected boolean last = false; // Set after the last line is read |
| |
| |
| /** |
| * Initializes the lexer to read up to (and including) the marker |
| * on a line by itself. |
| */ |
| |
| public HereDocLexer( String marker ) |
| { |
| if( marker.startsWith("-") ) |
| { |
| this.marker = marker.substring( 1, marker.length() ); |
| this.onmargin = false; |
| } |
| else |
| { |
| this.marker = marker; |
| this.onmargin = true; |
| } |
| } |
| |
| |
| |
| /** |
| * Sets the source lexer and sets the lexer running. |
| */ |
| |
| public void setSource( Lexer source ) |
| { |
| super.setSource( source ); |
| |
| data = ""; |
| consumed = -1; |
| last = false; |
| |
| restart(); |
| delimit( true ); |
| } |
| |
| |
| |
| /** |
| * Unsets the source lexer. |
| */ |
| |
| public void unsetSource() |
| { |
| finish(); |
| super.unsetSource(); |
| } |
| |
| |
| |
| /** |
| * Sets delimiting on. The first thing we to is check for and eat our |
| * delimiter. |
| */ |
| |
| public void delimit( boolean delimit ) |
| { |
| super.delimit( delimit ); |
| |
| if( delimit ) |
| { |
| try |
| { |
| if( !finished && la(1) == CharStream.EOS ) |
| { |
| finish(); |
| } |
| } |
| catch( Exception e ) |
| { |
| finished = true; |
| } |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Returns the next <code>k</code>th character, without consuming any. |
| */ |
| |
| public char la(int k) throws LexerException, ReadException |
| { |
| |
| if( !finished && source != null ) |
| { |
| if( consumed + k >= data.length() ) |
| { |
| refill(); |
| } |
| |
| if( consumed + k < data.length() ) |
| { |
| return data.charAt( consumed + k ); |
| } |
| } |
| |
| return CharStream.EOS; |
| } |
| |
| |
| |
| |
| /** |
| * Eats a character from the input stream. Searches for the delimiter if |
| * filtered. Note that turning delimiting on also checks if we are at the |
| * delimiter, so if we aren't finished, there is something to consume. |
| */ |
| |
| public char consume() throws LexerException, ReadException |
| { |
| if( !finished && source != null ) |
| { |
| char c = data.charAt( ++consumed ); |
| if( delimited && la(1) == CharStream.EOS ) |
| { |
| finish(); |
| } |
| |
| return c; |
| } |
| |
| return CharStream.EOS; |
| } |
| |
| |
| |
| /** |
| * Reads the next line from the underlying stream. If delimited, checks for |
| * the marker. We don't update finished here, though, as that would prevent |
| * any buffered data from being read. |
| */ |
| |
| protected void refill() throws LexerException, ReadException |
| { |
| if( !finished && source != null && !last ) |
| { |
| StringBuffer read = new StringBuffer(); |
| |
| // |
| // Read any residual data into the buffer. |
| |
| for( int i = consumed + 1; i < data.length(); i++ ) |
| { |
| read.append( data.charAt(i) ); |
| } |
| |
| |
| // |
| // Read line ends until we have some non-blank lines to read. |
| // Note that we have to be careful with the line ends, as the |
| // end of one line belongs to the next, when it comes to discards |
| // due to marker identification! |
| |
| char c; |
| StringBuffer raw = new StringBuffer(); |
| while( (c = source.la()) == '\n' || c == '\r' ) |
| { |
| if( raw.length() > 0 ) |
| { |
| read.append( raw ); |
| raw.setLength( 0 ); |
| } |
| |
| if( !((LexerBase)source).readEOL(raw) ) // bad cast, but for now... |
| { |
| throw new UnterminatedStringLiteralException(getStartLine(), getStartColumn()); |
| } |
| } |
| |
| |
| // |
| // Read the next line, checking for the end marker, if delimited. |
| // We leave the EOL for the next read... |
| |
| boolean use = true; |
| |
| if( !isDelimited() ) |
| { |
| while( (c = source.la()) != '\n' && c != '\r' && c != CharStream.EOS ) |
| { |
| raw.append( source.consume() ); |
| } |
| } |
| |
| else |
| { |
| // |
| // If the marker started with the "-" modifier, whitespace is |
| // allowed before the marker. The marker can be followed on |
| // the same line by code, so if it matches the beginning |
| // pattern, we stop after reading the last character. |
| |
| if( !onmargin ) |
| { |
| while( (c = source.la()) == ' ' || c == '\t' ) |
| { |
| raw.append( source.consume() ); |
| } |
| } |
| |
| int testing = 0, length = marker.length(); |
| boolean found = false, lost = false; |
| while( (c = source.la()) != '\n' && c != '\r' && c != CharStream.EOS && !found ) |
| { |
| if( !lost && c == marker.charAt(testing) ) |
| { |
| testing++; |
| if( testing == length ) |
| { |
| found = true; |
| } |
| } |
| else |
| { |
| lost = true; |
| } |
| |
| raw.append( source.consume() ); |
| } |
| |
| if( found ) |
| { |
| use = false; |
| } |
| } |
| |
| |
| // |
| // It's either our delimiter or a line of data. |
| |
| if( use ) |
| { |
| read.append( raw ); |
| } |
| else |
| { |
| last = true; |
| } |
| |
| |
| data = read.toString(); |
| consumed = -1; |
| } |
| } |
| |
| } |