| /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * |
| * The contents of this file are subject to the Netscape Public |
| * License Version 1.1 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of |
| * the License at http://www.mozilla.org/NPL/ |
| * |
| * Software distributed under the License is distributed on an "AS |
| * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| * implied. See the License for the specific language governing |
| * rights and limitations under the License. |
| * |
| * The Original Code is Rhino code, released |
| * May 6, 1999. |
| * |
| * The Initial Developer of the Original Code is Netscape |
| * Communications Corporation. Portions created by Netscape are |
| * Copyright (C) 1997-1999 Netscape Communications Corporation. All |
| * Rights Reserved. |
| * |
| * Contributor(s): |
| * Mike McCabe |
| * |
| * Alternatively, the contents of this file may be used under the |
| * terms of the GNU Public License (the "GPL"), in which case the |
| * provisions of the GPL are applicable instead of those above. |
| * If you wish to allow use of your version of this file only |
| * under the terms of the GPL and not to allow others to use your |
| * version of this file under the NPL, indicate your decision by |
| * deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL. If you do not delete |
| * the provisions above, a recipient may use your version of this |
| * file under either the NPL or the GPL. |
| */ |
| // Modified by Google |
| |
| package com.google.gwt.dev.js.rhino; |
| |
| import java.io.Reader; |
| import java.io.IOException; |
| |
| /** |
| * An input buffer that combines fast character-based access with |
| * (slower) support for retrieving the text of the current line. It |
| * also supports building strings directly out of the internal buffer |
| * to support fast scanning with minimal object creation. |
| * |
| * Note that it is customized in several ways to support the |
| * TokenStream class, and should not be considered general. |
| * |
| * Credits to Kipp Hickman and John Bandhauer. |
| * |
| * @author Mike McCabe |
| */ |
| final class LineBuffer { |
| /* |
| * for smooth operation of getLine(), this should be greater than |
| * the length of any expected line. Currently, 256 is 3% slower |
| * than 4096 for large compiles, but seems safer given evaluateString. |
| * Strings for the scanner are are built with StringBuffers |
| * instead of directly out of the buffer whenever a string crosses |
| * a buffer boundary, so small buffer sizes will mean that more |
| * objects are created. |
| */ |
| static final int BUFLEN = 256; |
| |
| LineBuffer(Reader in, int lineno) { |
| this.in = in; |
| this.lineno = lineno; |
| } |
| |
| int read() throws IOException { |
| for(;;) { |
| if (end == offset && !fill()) |
| return -1; |
| |
| int c = buffer[offset]; |
| ++offset; |
| |
| if ((c & EOL_HINT_MASK) == 0) { |
| switch (c) { |
| case '\r': |
| // if the next character is a newline, skip past it. |
| if (offset != end) { |
| if (buffer[offset] == '\n') |
| ++offset; |
| } else { |
| // set a flag for fill(), in case the first char |
| // of the next fill is a newline. |
| lastWasCR = true; |
| } |
| // NO break here! |
| case '\n': case '\u2028': case '\u2029': |
| prevStart = lineStart; |
| lineStart = offset; |
| lineno++; |
| return '\n'; |
| } |
| } |
| |
| if (c < 128 || !formatChar(c)) { |
| return c; |
| } |
| } |
| } |
| |
| void unread() { |
| // offset can only be 0 when we're asked to unread() an implicit |
| // EOF_CHAR. |
| |
| // This would be wrong behavior in the general case, |
| // because a peek() could map a buffer.length offset to 0 |
| // in the process of a fill(), and leave it there. But |
| // the scanner never calls peek() or a failed match() |
| // followed by unread()... this would violate 1-character |
| // lookahead. |
| if (offset == 0 && !hitEOF) Context.codeBug(); |
| |
| if (offset == 0) // Same as if (hitEOF) |
| return; |
| offset--; |
| int c = buffer[offset]; |
| if ((c & EOL_HINT_MASK) == 0 && eolChar(c)) { |
| lineStart = prevStart; |
| lineno--; |
| } |
| } |
| |
| private void skipFormatChar() { |
| if (checkSelf && !formatChar(buffer[offset])) Context.codeBug(); |
| |
| // swap prev character with format one so possible call to |
| // startString can assume that previous non-format char is at |
| // offset - 1. Note it causes getLine to return not exactly the |
| // source LineBuffer read, but it is used only in error reporting |
| // and should not be a problem. |
| if (offset != 0) { |
| char tmp = buffer[offset]; |
| buffer[offset] = buffer[offset - 1]; |
| buffer[offset - 1] = tmp; |
| } |
| else if (otherEnd != 0) { |
| char tmp = buffer[offset]; |
| buffer[offset] = otherBuffer[otherEnd - 1]; |
| otherBuffer[otherEnd - 1] = tmp; |
| } |
| |
| ++offset; |
| } |
| |
| int peek() throws IOException { |
| for (;;) { |
| if (end == offset && !fill()) { |
| return -1; |
| } |
| |
| int c = buffer[offset]; |
| if ((c & EOL_HINT_MASK) == 0 && eolChar(c)) { |
| return '\n'; |
| } |
| if (c < 128 || !formatChar(c)) { |
| return c; |
| } |
| |
| skipFormatChar(); |
| } |
| } |
| |
| boolean match(int test) throws IOException { |
| // TokenStream never looks ahead for '\n', which allows simple code |
| if ((test & EOL_HINT_MASK) == 0 && eolChar(test)) |
| Context.codeBug(); |
| // Format chars are not allowed either |
| if (test >= 128 && formatChar(test)) |
| Context.codeBug(); |
| |
| for (;;) { |
| if (end == offset && !fill()) |
| return false; |
| |
| int c = buffer[offset]; |
| if (test == c) { |
| ++offset; |
| return true; |
| } |
| if (c < 128 || !formatChar(c)) { |
| return false; |
| } |
| skipFormatChar(); |
| } |
| } |
| |
| // Reconstruct a source line from the buffers. This can be slow... |
| String getLine() { |
| // Look for line end in the unprocessed buffer |
| int i = offset; |
| while(true) { |
| if (i == end) { |
| // if we're out of buffer, let's just expand it. We do |
| // this instead of reading into a StringBuffer to |
| // preserve the stream for later reads. |
| if (end == buffer.length) { |
| char[] tmp = new char[buffer.length * 2]; |
| System.arraycopy(buffer, 0, tmp, 0, end); |
| buffer = tmp; |
| } |
| int charsRead = 0; |
| try { |
| charsRead = in.read(buffer, end, buffer.length - end); |
| } catch (IOException ioe) { |
| // ignore it, we're already displaying an error... |
| break; |
| } |
| if (charsRead < 0) |
| break; |
| end += charsRead; |
| } |
| int c = buffer[i]; |
| if ((c & EOL_HINT_MASK) == 0 && eolChar(c)) |
| break; |
| i++; |
| } |
| |
| int start = lineStart; |
| if (lineStart < 0) { |
| // the line begins somewhere in the other buffer; get that first. |
| StringBuffer sb = new StringBuffer(otherEnd - otherStart + i); |
| sb.append(otherBuffer, otherStart, otherEnd - otherStart); |
| sb.append(buffer, 0, i); |
| return sb.toString(); |
| } else { |
| return new String(buffer, lineStart, i - lineStart); |
| } |
| } |
| |
| // Get the offset of the current character, relative to |
| // the line that getLine() returns. |
| int getOffset() { |
| if (lineStart < 0) |
| // The line begins somewhere in the other buffer. |
| return offset + (otherEnd - otherStart); |
| else |
| return offset - lineStart; |
| } |
| |
| private boolean fill() throws IOException { |
| // fill should be caled only for emty buffer |
| if (checkSelf && !(end == offset)) Context.codeBug(); |
| |
| // swap buffers |
| char[] tempBuffer = buffer; |
| buffer = otherBuffer; |
| otherBuffer = tempBuffer; |
| |
| // allocate the buffers lazily, in case we're handed a short string. |
| if (buffer == null) { |
| buffer = new char[BUFLEN]; |
| } |
| |
| // buffers have switched, so move the newline marker. |
| if (lineStart >= 0) { |
| otherStart = lineStart; |
| } else { |
| // discard beging of the old line |
| otherStart = 0; |
| } |
| |
| otherEnd = end; |
| |
| // set lineStart to a sentinel value, unless this is the first |
| // time around. |
| prevStart = lineStart = (otherBuffer == null) ? 0 : -1; |
| |
| offset = 0; |
| end = in.read(buffer, 0, buffer.length); |
| if (end < 0) { |
| end = 0; |
| |
| // can't null buffers here, because a string might be retrieved |
| // out of the other buffer, and a 0-length string might be |
| // retrieved out of this one. |
| |
| hitEOF = true; |
| return false; |
| } |
| |
| // If the last character of the previous fill was a carriage return, |
| // then ignore a newline. |
| |
| // There's another bizzare special case here. If lastWasCR is |
| // true, and we see a newline, and the buffer length is |
| // 1... then we probably just read the last character of the |
| // file, and returning after advancing offset is not the right |
| // thing to do. Instead, we try to ignore the newline (and |
| // likely get to EOF for real) by doing yet another fill(). |
| if (lastWasCR) { |
| if (buffer[0] == '\n') { |
| offset++; |
| if (end == 1) |
| return fill(); |
| } |
| lineStart = offset; |
| lastWasCR = false; |
| } |
| return true; |
| } |
| |
| int getLineno() { return lineno; } |
| boolean eof() { return hitEOF; } |
| |
| private static boolean formatChar(int c) { |
| return Character.getType((char)c) == Character.FORMAT; |
| } |
| |
| private static boolean eolChar(int c) { |
| return c == '\r' || c == '\n' || c == '\u2028' || c == '\u2029'; |
| } |
| |
| // Optimization for faster check for eol character: eolChar(c) returns |
| // true only when (c & EOL_HINT_MASK) == 0 |
| private static final int EOL_HINT_MASK = 0xdfd0; |
| |
| private Reader in; |
| private char[] otherBuffer = null; |
| private char[] buffer = null; |
| |
| // Yes, there are too too many of these. |
| private int offset = 0; |
| private int end = 0; |
| private int otherEnd; |
| private int lineno; |
| |
| private int lineStart = 0; |
| private int otherStart = 0; |
| private int prevStart = 0; |
| |
| private boolean lastWasCR = false; |
| private boolean hitEOF = false; |
| |
| // Rudimentary support for Design-by-Contract |
| private static final boolean checkSelf = true; |
| } |