2002/03/21 01:19:33
[org.ibex.core.git] / src / org / mozilla / javascript / LineBuffer.java
diff --git a/src/org/mozilla/javascript/LineBuffer.java b/src/org/mozilla/javascript/LineBuffer.java
new file mode 100644 (file)
index 0000000..d3f0ec4
--- /dev/null
@@ -0,0 +1,412 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-\r
+ *\r
+ * The contents of this file are subject to the Netscape Public\r
+ * License Version 1.1 (the "License"); you may not use this file\r
+ * except in compliance with the License. You may obtain a copy of\r
+ * the License at http://www.mozilla.org/NPL/\r
+ *\r
+ * Software distributed under the License is distributed on an "AS\r
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr\r
+ * implied. See the License for the specific language governing\r
+ * rights and limitations under the License.\r
+ *\r
+ * The Original Code is Rhino code, released\r
+ * May 6, 1999.\r
+ *\r
+ * The Initial Developer of the Original Code is Netscape\r
+ * Communications Corporation.  Portions created by Netscape are\r
+ * Copyright (C) 1997-1999 Netscape Communications Corporation. All\r
+ * Rights Reserved.\r
+ *\r
+ * Contributor(s): \r
+ * Mike McCabe\r
+ *\r
+ * Alternatively, the contents of this file may be used under the\r
+ * terms of the GNU Public License (the "GPL"), in which case the\r
+ * provisions of the GPL are applicable instead of those above.\r
+ * If you wish to allow use of your version of this file only\r
+ * under the terms of the GPL and not to allow others to use your\r
+ * version of this file under the NPL, indicate your decision by\r
+ * deleting the provisions above and replace them with the notice\r
+ * and other provisions required by the GPL.  If you do not delete\r
+ * the provisions above, a recipient may use your version of this\r
+ * file under either the NPL or the GPL.\r
+ */\r
+\r
+package org.mozilla.javascript;\r
+\r
+import java.io.Reader;\r
+import java.io.IOException;\r
+\r
+/**\r
+ * An input buffer that combines fast character-based access with\r
+ * (slower) support for retrieving the text of the current line.  It\r
+ * also supports building strings directly out of the internal buffer\r
+ * to support fast scanning with minimal object creation.\r
+ *\r
+ * Note that it is customized in several ways to support the\r
+ * TokenStream class, and should not be considered general.\r
+ *\r
+ * Credits to Kipp Hickman and John Bandhauer.\r
+ *\r
+ * @author Mike McCabe\r
+ */\r
+final class LineBuffer {\r
+    /*\r
+     * for smooth operation of getLine(), this should be greater than\r
+     * the length of any expected line.  Currently, 256 is 3% slower\r
+     * than 4096 for large compiles, but seems safer given evaluateString.\r
+     * Strings for the scanner are are built with StringBuffers\r
+     * instead of directly out of the buffer whenever a string crosses\r
+     * a buffer boundary, so small buffer sizes will mean that more\r
+     * objects are created.\r
+     */\r
+    static final int BUFLEN = 256;\r
+\r
+    LineBuffer(Reader in, int lineno) {\r
+        this.in = in;\r
+        this.lineno = lineno;\r
+    }\r
+\r
+    int read() throws IOException {\r
+               for(;;) {\r
+                       if (end == offset && !fill())\r
+                           return -1;\r
+\r
+                       // Do only a bitmask + branch per character, at the cost of\r
+                       // three branches per low-bits-only (or 2028/9) character.\r
+            if ((buffer[offset] & '\udfd0') == 0) {\r
+                           if (buffer[offset] == '\r') {\r
+                               // if the next character is a newline, skip past it.\r
+                               if ((offset + 1) < end) {\r
+                                   if (buffer[offset + 1] == '\n')\r
+                                       offset++;\r
+                               } else {\r
+                                   // set a flag for fill(), in case the first char of the\r
+                                   // next fill is a newline.\r
+                                   lastWasCR = true;\r
+                               }\r
+                           }\r
+                else \r
+                    if ((buffer[offset] != '\n') \r
+                        && (buffer[offset] != '\u2028')\r
+                        && (buffer[offset] != '\u2029'))\r
+                    { \r
+                        if (Character.getType(buffer[offset])\r
+                                                    == Character.FORMAT) {\r
+                                           hadCFSinceStringStart = true;\r
+                                           offset++;\r
+                            continue;\r
+                        }\r
+                        return (int) buffer[offset++];\r
+                               }\r
+                           offset++;\r
+                           prevStart = lineStart;\r
+                           lineStart = offset;\r
+                           lineno++;\r
+                           return '\n';\r
+                       }\r
+                       if ((buffer[offset] >= 128) \r
+                                 && (Character.getType(buffer[offset]) == Character.FORMAT)) {\r
+                               hadCFSinceStringStart = true;\r
+                               offset++;\r
+                       }\r
+                       else\r
+                               break;\r
+               }\r
+               \r
+        return (int) buffer[offset++];\r
+    }\r
+\r
+    void unread() {\r
+        if (offset == 0)\r
+            // We can get here when we're asked to unread() an\r
+            // implicit EOF_CHAR.\r
+            \r
+            // This would also be wrong behavior in the general case,\r
+            // because a peek() could map a buffer.length offset to 0\r
+            // in the process of a fill(), and leave it there.  But\r
+            // the scanner never calls peek() or a failed match()\r
+            // followed by unread()... this would violate 1-character\r
+            // lookahead.  So we're OK.\r
+            return;\r
+        offset--;\r
+        if ((buffer[offset] & '\ufff0') == 0\r
+            && (buffer[offset] == '\r' || buffer[offset] == '\n')) {\r
+            // back off from the line start we presumably just registered...\r
+            lineStart = prevStart;\r
+            lineno--;\r
+        }\r
+    }\r
+\r
+    int peek() throws IOException {\r
+        if (end == offset && !fill())\r
+            return -1;\r
+\r
+        if (buffer[offset] == '\r')\r
+            return '\n';\r
+\r
+        return buffer[offset];\r
+    }\r
+\r
+    boolean match(char c) throws IOException {\r
+        if (end == offset && !fill())\r
+            return false;\r
+\r
+        // This'd be a place where we'd need to map '\r' to '\n' and\r
+        // do other updates, but TokenStream never looks ahead for\r
+        // '\n', so we don't bother.\r
+        if (buffer[offset] == c) {\r
+            offset++;\r
+            return true;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    // Reconstruct a source line from the buffers.  This can be slow...\r
+    String getLine() {\r
+        StringBuffer result = new StringBuffer();\r
+\r
+        int start = lineStart;\r
+        if (start >= offset) {\r
+            // the line begins somewhere in the other buffer; get that first.\r
+            if (otherStart < otherEnd)\r
+                // if a line ending was seen in the other buffer... otherwise\r
+                // just ignore this strange case.\r
+                result.append(otherBuffer, otherStart,\r
+                              otherEnd - otherStart);\r
+            start = 0;\r
+        }\r
+\r
+        // get the part of the line in the current buffer.\r
+        result.append(buffer, start, offset - start);\r
+\r
+        // Get the remainder of the line.\r
+        int i = offset;\r
+        while(true) {\r
+            if (i == buffer.length) {\r
+                // we're out of buffer, let's just expand it.  We do\r
+                // this instead of reading into a StringBuffer to\r
+                // preserve the stream for later reads.\r
+                char[] newBuffer = new char[buffer.length * 2];\r
+                System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);\r
+                buffer = newBuffer;\r
+                int charsRead = 0;\r
+                try {\r
+                    charsRead = in.read(buffer, end, buffer.length - end);\r
+                } catch (IOException ioe) {\r
+                    // ignore it, we're already displaying an error...\r
+                }\r
+                if (charsRead < 0)\r
+                    break;\r
+                end += charsRead;\r
+            }\r
+            if (buffer[i] == '\r' || buffer[i] == '\n')\r
+                break;\r
+            i++;\r
+        }\r
+\r
+        result.append(buffer, offset, i - offset);\r
+        return result.toString();\r
+    }\r
+\r
+    // Get the offset of the current character, relative to\r
+    // the line that getLine() returns.\r
+    int getOffset() {\r
+        if (lineStart >= offset)\r
+            // The line begins somewhere in the other buffer.\r
+            return offset + (otherEnd - otherStart);\r
+        else\r
+            return offset - lineStart;\r
+    }\r
+\r
+    // Set a mark to indicate that the reader should begin\r
+    // accumulating characters for getString().  The string begins\r
+    // with the last character read.\r
+    void startString() {\r
+        if (offset == 0) {\r
+            // We can get here if startString is called after a peek()\r
+            // or failed match() with offset past the end of the\r
+            // buffer.\r
+\r
+            // We're at the beginning of the buffer, and the previous character\r
+            // (which we want to include) is at the end of the last one, so\r
+            // we just go to StringBuffer mode.\r
+            stringSoFar = new StringBuffer();\r
+                       \r
+            stringSoFar.append(otherBuffer, otherEnd - 1, 1);\r
+\r
+            stringStart = -1; // Set sentinel value.\r
+                       hadCFSinceStringStart = ((otherBuffer[otherEnd - 1] >= 128) \r
+                                       && Character.getType(otherBuffer[otherEnd - 1])\r
+                                                                                                       == Character.FORMAT);\r
+        } else {\r
+            // Support restarting strings\r
+            stringSoFar = null;\r
+            stringStart = offset - 1;\r
+                       hadCFSinceStringStart = ((buffer[stringStart] >= 128) \r
+                                       && Character.getType(buffer[stringStart]) == Character.FORMAT);\r
+        }\r
+               \r
+    }\r
+\r
+    // Get a string consisting of the characters seen since the last\r
+    // startString.\r
+    String getString() {\r
+        String result;\r
+\r
+        /*\r
+         * There's one strange case here:  If the character offset currently\r
+         * points to (which we never want to include in the string) is\r
+         * a newline, then if the previous character is a carriage return,\r
+         * we probably want to exclude that as well.  If the offset is 0,\r
+         * then we hope that fill() handled excluding it from stringSoFar.\r
+         */\r
+        int loseCR = (offset > 0 &&\r
+                      buffer[offset] == '\n' && buffer[offset - 1] == '\r') ?\r
+            1 : 0;\r
+\r
+        if (stringStart != -1) {\r
+            // String mark is valid, and in this buffer.\r
+\r
+            result = new String(buffer, stringStart, \r
+                                offset - stringStart - loseCR);\r
+        } else {\r
+            if (stringSoFar == null) \r
+                stringSoFar = new StringBuffer();\r
+            // Exclude cr as well as nl of newline.  If offset is 0, then\r
+            // hopefully fill() did the right thing.\r
+            result = (stringSoFar.append(buffer, 0, offset - loseCR)).toString();\r
+        }\r
+        \r
+        stringStart = -1;\r
+        stringSoFar = null;\r
+               \r
+               if (hadCFSinceStringStart) {\r
+                       char c[] = result.toCharArray();\r
+                       StringBuffer x = null;\r
+                       for (int i = 0; i < c.length; i++) {\r
+                               if (Character.getType(c[i]) == Character.FORMAT) {\r
+                                       if (x == null) {\r
+                                               x = new StringBuffer();\r
+                                               x.append(c, 0, i);\r
+                                       }\r
+                               }\r
+                               else\r
+                                       if (x != null) x.append(c[i]);\r
+                       }\r
+                       if (x != null) result = x.toString();   \r
+               }\r
+               \r
+        return result;\r
+    }            \r
+\r
+    boolean fill() throws IOException {\r
+        // not sure I care...\r
+        if (end - offset != 0) \r
+            throw new IOException("fill of non-empty buffer");\r
+\r
+        // If there's a string currently being accumulated, save\r
+        // off the progress.\r
+\r
+        /*\r
+         * Exclude an end-of-buffer carriage return.  NOTE this is not\r
+         * fully correct in the general case, because we really only\r
+         * want to exclude the carriage return if it's followed by a\r
+         * linefeed at the beginning of the next buffer.  But we fudge\r
+         * because the scanner doesn't do this.\r
+         */\r
+        int loseCR = (offset > 0 && lastWasCR) ? 1 : 0;\r
+\r
+        if (stringStart != -1) {\r
+            // The mark is in the current buffer, save off from the mark to the\r
+            // end.\r
+            stringSoFar = new StringBuffer();\r
+\r
+            stringSoFar.append(buffer, stringStart, end - stringStart - loseCR);\r
+            stringStart = -1;\r
+        } else if (stringSoFar != null) {\r
+            // the string began prior to the current buffer, so save the\r
+            // whole current buffer.\r
+            stringSoFar.append(buffer, 0, end - loseCR);\r
+        }\r
+\r
+        // swap buffers\r
+        char[] tempBuffer = buffer;\r
+        buffer = otherBuffer;\r
+        otherBuffer = tempBuffer;\r
+\r
+        // allocate the buffers lazily, in case we're handed a short string.\r
+        if (buffer == null) {\r
+            buffer = new char[BUFLEN];\r
+        }\r
+\r
+        // buffers have switched, so move the newline marker.\r
+        otherStart = lineStart;\r
+        otherEnd = end;\r
+\r
+        // set lineStart to a sentinel value, unless this is the first\r
+        // time around.\r
+        prevStart = lineStart = (otherBuffer == null) ? 0 : buffer.length + 1;\r
+        \r
+        offset = 0;\r
+        end = in.read(buffer, 0, buffer.length);\r
+        if (end < 0) {\r
+            end = 0;\r
+\r
+            // can't null buffers here, because a string might be retrieved\r
+            // out of the other buffer, and a 0-length string might be\r
+            // retrieved out of this one.\r
+\r
+            hitEOF = true;\r
+            return false;\r
+        }\r
+\r
+        // If the last character of the previous fill was a carriage return,\r
+        // then ignore a newline.\r
+\r
+        // There's another bizzare special case here.  If lastWasCR is\r
+        // true, and we see a newline, and the buffer length is\r
+        // 1... then we probably just read the last character of the\r
+        // file, and returning after advancing offset is not the right\r
+        // thing to do.  Instead, we try to ignore the newline (and\r
+        // likely get to EOF for real) by doing yet another fill().\r
+        if (lastWasCR) {\r
+            if (buffer[0] == '\n') {\r
+              offset++;\r
+              if (end == 1)\r
+                  return fill();\r
+            }\r
+            lineStart = offset;\r
+            lastWasCR = false;\r
+        }\r
+        return true;\r
+    }\r
+\r
+    int getLineno() { return lineno; }\r
+    boolean eof() { return hitEOF; }\r
+    \r
+    private Reader in;\r
+    private char[] otherBuffer = null;\r
+    private char[] buffer = null;\r
+\r
+    // Yes, there are too too many of these.\r
+    private int offset = 0;\r
+    private int end = 0;\r
+    private int otherEnd;\r
+    private int lineno;\r
+\r
+    private int lineStart = 0;\r
+    private int otherStart = 0;\r
+    private int prevStart = 0;\r
+    \r
+    private boolean lastWasCR = false;\r
+    private boolean hitEOF = false;\r
+\r
+    private int stringStart = -1;\r
+    private StringBuffer stringSoFar = null;\r
+       private boolean hadCFSinceStringStart = false;\r
+\r
+}\r
+\r
+\r