blob: 4424d18ba09db45cbe366679f01f7282e6015a81 [file] [log] [blame]
/* -*- 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;
}