added GnuRegexp.java to eliminated dependency on gnu.regexp.jar
authoradam <adam@megacz.com>
Sun, 5 Sep 2004 07:01:48 +0000 (07:01 +0000)
committeradam <adam@megacz.com>
Sun, 5 Sep 2004 07:01:48 +0000 (07:01 +0000)
darcs-hash:20040905070148-5007d-fbfdc43f446660a114fb6516be59da3d6a60eb5e.gz

src/org/ibex/js/Fountain.java [moved from src/org/ibex/js/Stream.java with 81% similarity]
src/org/ibex/js/GnuRegexp.java [new file with mode: 0644]
src/org/ibex/js/JSReflection.java
src/org/ibex/js/JSRegexp.java

similarity index 81%
rename from src/org/ibex/js/Stream.java
rename to src/org/ibex/js/Fountain.java
index 6c9a806..264f2ff 100644 (file)
@@ -4,21 +4,19 @@ package org.ibex.js;
 import java.io.*;
 import java.util.zip.*;
 import org.ibex.util.*;
-import org.ibex.plat.*;
 import org.ibex.net.*;
-import org.ibex.core.*;
 
 /**
  *   Essentiall an InputStream "factory".  You can repeatedly ask a
- *   Stream for an InputStream, and each InputStream you get back will
+ *   Fountain for an InputStream, and each InputStream you get back will
  *   be totally independent of the others (ie separate stream position
  *   and state) although they draw from the same data source.
  */
-public abstract class Stream extends JS.Cloneable {
+public abstract class Fountain extends JS.Cloneable {
 
     // Public Interface //////////////////////////////////////////////////////////////////////////////
 
-    public static InputStream getInputStream(Object js) throws IOException { return ((Stream)((JS)js).unclone()).getInputStream();}
+    public static InputStream getInputStream(Object js) throws IOException { return ((Fountain)((JS)js).unclone()).getInputStream();}
     public static class NotCacheableException extends Exception { }
 
     // streams are "sealed" by default to prevent accidental object leakage
@@ -37,17 +35,17 @@ public abstract class Stream extends JS.Cloneable {
     protected String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); }
 
     /** HTTP or HTTPS resource */
-    public static class HTTP extends Stream {
+    public static class HTTP extends Fountain {
         private String url;
         public String toString() { return "Stream.HTTP:" + url; }
         public HTTP(String url) { while (url.endsWith("/")) url = url.substring(0, url.length() - 1); this.url = url; }
         public Object _get(Object key) { return new HTTP(url + "/" + (String)key); }
         public String getCacheKey(Vec path) throws NotCacheableException { return url; }
-        public InputStream getInputStream() throws IOException { return new org.ibex.net.HTTP(url).GET(); }
+        public InputStream getInputStream() throws IOException { return new org.ibex.net.HTTP(url).GET(null, null); }
     }
 
     /** byte arrays */
-    public static class ByteArray extends Stream {
+    public static class ByteArray extends Fountain {
         private byte[] bytes;
         private String cacheKey;
         public ByteArray(byte[] bytes, String cacheKey) { this.bytes = bytes; this.cacheKey = cacheKey; }
@@ -57,7 +55,7 @@ public abstract class Stream extends JS.Cloneable {
     }
 
     /** a file */
-    public static class File extends Stream {
+    public static class File extends Fountain {
         private String path;
         public File(String path) { this.path = path; }
         public String toString() { return "file:" + path; }
@@ -67,11 +65,11 @@ public abstract class Stream extends JS.Cloneable {
     }
 
     /** "unwrap" a Zip archive */
-    public static class Zip extends Stream {
-        private Stream parent;
+    public static class Zip extends Fountain {
+        private Fountain parent;
         private String path;
-        public Zip(Stream parent) { this(parent, null); }
-        public Zip(Stream parent, String path) {
+        public Zip(Fountain parent) { this(parent, null); }
+        public Zip(Fountain parent, String path) {
             while(path != null && path.startsWith("/")) path = path.substring(1);
             this.parent = parent;
             this.path = path;
@@ -89,27 +87,32 @@ public abstract class Stream extends JS.Cloneable {
     }
 
     /** "unwrap" a Cab archive */
-    public static class Cab extends Stream {
-        private Stream parent;
+    /* temporarily disabled due to dependency issues
+    public static class Cab extends Fountain {
+        private Fountain parent;
         private String path;
-        public Cab(Stream parent) { this(parent, null); }
-        public Cab(Stream parent, String path) { this.parent = parent; this.path = path; }
+        public Cab(Fountain parent) { this(parent, null); }
+        public Cab(Fountain parent, String path) { this.parent = parent; this.path = path; }
         public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!cab:"; }
         public Object _get(Object key) { return new Cab(parent, path==null?(String)key:path+'/'+(String)key); }
         public InputStream getInputStream() throws IOException { return new MSPack(parent.getInputStream()).getInputStream(path); }
     }
+    */
 
     /** the Builtin resource */
-    public static class Builtin extends Stream {
+    /* temporarily disabled due to dependency issues
+    public static class Builtin extends Fountain {
         public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); }
         public InputStream getInputStream() throws IOException { return Platform.getBuiltinInputStream(); }
     }
+    */
 
     /** shadow resource which replaces the graft */
-    public static class ProgressWatcher extends Stream {
-        final Stream watchee;
+    /* temporarily disabled due to dependency issues
+    public static class ProgressWatcher extends Fountain {
+        final Fountain watchee;
         JS callback;
-        public ProgressWatcher(Stream watchee, JS callback) { this.watchee = watchee; this.callback = callback; }
+        public ProgressWatcher(Fountain watchee, JS callback) { this.watchee = watchee; this.callback = callback; }
         public String getCacheKey() throws NotCacheableException { return watchee.getCacheKey(); }
         public InputStream getInputStream() throws IOException {
             final InputStream is = watchee.getInputStream();
@@ -132,15 +135,17 @@ public abstract class Stream extends JS.Cloneable {
                 };
         }
     }
+    */
 
     /** subclass from this if you want a CachedInputStream for each path */
-    public static class CachedStream extends Stream {
-        private Stream parent;
+    /* temporarily disabled due to dependency issues
+    public static class CachedStream extends Fountain {
+        private Fountain parent;
         private boolean disk = false;
         private String key;
         public String getCacheKey() throws NotCacheableException { return key; }
         CachedInputStream cis = null;
-        public CachedStream(Stream p, String s, boolean d) throws NotCacheableException {
+        public CachedStream(Fountain p, String s, boolean d) throws NotCacheableException {
             this.parent = p; this.disk = d; this.key = p.getCacheKey();
         }
         public InputStream getInputStream() throws IOException {
@@ -155,4 +160,5 @@ public abstract class Stream extends JS.Cloneable {
             return cis.getInputStream();
         }
     }
+    */
 }
diff --git a/src/org/ibex/js/GnuRegexp.java b/src/org/ibex/js/GnuRegexp.java
new file mode 100644 (file)
index 0000000..033f320
--- /dev/null
@@ -0,0 +1,4183 @@
+// This file is (approximately) the concatenation of all the source
+// files in the src/gnu/regexp directory of GNU Regexp, version 1.1.4
+
+// Although doing this is a huge hack, it simplifies the deployment of
+// applications that depend on org.ibex.js.
+
+package org.ibex.js;
+import java.util.*;
+import java.io.*;
+import java.text.*;
+
+public class GnuRegexp {
+    /*
+     *  gnu/regexp/CharIndexed.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    /**
+     * Defines the interface used internally so that different types of source
+     * text can be accessed in the same way.  Built-in concrete classes provide
+     * support for String, StringBuffer, InputStream and char[] types.
+     * A class that is CharIndexed supports the notion of a cursor within a
+     * block of text.  The cursor must be able to be advanced via the move()
+     * method.  The charAt() method returns the character at the cursor position
+     * plus a given offset.
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     */
+    public static interface CharIndexed {
+        /**
+         * Defines a constant (0xFFFF was somewhat arbitrarily chosen)
+         * that can be returned by the charAt() function indicating that
+         * the specified index is out of range.
+         */
+        char OUT_OF_BOUNDS = '\uFFFF';
+
+        /**
+         * Returns the character at the given offset past the current cursor
+         * position in the input.  The index of the current position is zero.
+         * It is possible for this method to be called with a negative index.
+         * This happens when using the '^' operator in multiline matching mode
+         * or the '\b' or '\<' word boundary operators.  In any case, the lower
+         * bound is currently fixed at -2 (for '^' with a two-character newline).
+         *
+         * @param index the offset position in the character field to examine
+         * @return the character at the specified index, or the OUT_OF_BOUNDS
+         *   character defined by this interface.
+         */
+        char charAt(int index);
+
+        /**
+         * Shifts the input buffer by a given number of positions.  Returns
+         * true if the new cursor position is valid.
+         */
+        boolean move(int index);
+
+        /**
+         * Returns true if the most recent move() operation placed the cursor
+         * position at a valid position in the input.
+         */
+        boolean isValid();
+    }
+    /*
+     *  gnu/regexp/CharIndexedCharArray.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    static class CharIndexedCharArray implements CharIndexed, Serializable {
+        private char[] s;
+        private int anchor;
+    
+        CharIndexedCharArray(char[] str, int index) {
+            s = str;
+            anchor = index;
+        }
+    
+        public char charAt(int index) {
+            int pos = anchor + index;
+            return ((pos < s.length) && (pos >= 0)) ? s[pos] : OUT_OF_BOUNDS;
+        }
+    
+        public boolean isValid() {
+            return (anchor < s.length);
+        }
+    
+        public boolean move(int index) {
+            return ((anchor += index) < s.length);
+        }
+    }
+    /*
+     *  gnu/regexp/CharIndexedReader.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    // TODO: move(x) shouldn't rely on calling next() x times
+
+    static class CharIndexedInputStream implements CharIndexed {
+        private static final int BUFFER_INCREMENT = 1024;
+        private static final int UNKNOWN = Integer.MAX_VALUE; // value for end
+    
+        private BufferedInputStream br;
+
+        // so that we don't try to reset() right away
+        private int index = -1;
+
+        private int bufsize = BUFFER_INCREMENT;
+
+        private int end = UNKNOWN;
+
+        private char cached = OUT_OF_BOUNDS;
+
+        // Big enough for a \r\n pair
+        // lookBehind[0] = most recent
+        // lookBehind[1] = second most recent
+        private char[] lookBehind = new char[] { OUT_OF_BOUNDS, OUT_OF_BOUNDS }; 
+    
+        CharIndexedInputStream(InputStream str, int index) {
+            if (str instanceof BufferedInputStream) br = (BufferedInputStream) str;
+            else br = new BufferedInputStream(str,BUFFER_INCREMENT);
+            next();
+            if (index > 0) move(index);
+        }
+    
+        private boolean next() {
+            if (end == 1) return false;
+            end--; // closer to end
+
+            try {
+                if (index != -1) {
+                    br.reset();
+                }
+                int i = br.read();
+                br.mark(bufsize);
+                if (i == -1) {
+                    end = 1;
+                    cached = OUT_OF_BOUNDS;
+                    return false;
+                }
+                cached = (char) i;
+                index = 1;
+            } catch (IOException e) { 
+                e.printStackTrace();
+                cached = OUT_OF_BOUNDS;
+                return false; 
+            }
+            return true;
+        }
+    
+        public char charAt(int index) {
+            if (index == 0) {
+                return cached;
+            } else if (index >= end) {
+                return OUT_OF_BOUNDS;
+            } else if (index == -1) {
+                return lookBehind[0];
+            } else if (index == -2) {
+                return lookBehind[1];
+            } else if (index < -2) {
+                return OUT_OF_BOUNDS;
+            } else if (index >= bufsize) {
+                // Allocate more space in the buffer.
+                try {
+                    while (bufsize <= index) bufsize += BUFFER_INCREMENT;
+                    br.reset();
+                    br.mark(bufsize);
+                    br.skip(index-1);
+                } catch (IOException e) { }
+            } else if (this.index != index) {
+                try {
+                    br.reset();
+                    br.skip(index-1);
+                } catch (IOException e) { }
+            }
+            char ch = OUT_OF_BOUNDS;
+       
+            try {
+                int i = br.read();
+                this.index = index+1; // this.index is index of next pos relative to charAt(0)
+                if (i == -1) {
+                    // set flag that next should fail next time?
+                    end = index;
+                    return ch;
+                }
+                ch = (char) i;
+            } catch (IOException ie) { }
+       
+            return ch;
+        }
+    
+        public boolean move(int index) {
+            // move read position [index] clicks from 'charAt(0)'
+            boolean retval = true;
+            while (retval && (index-- > 0)) retval = next();
+            return retval;
+        }
+    
+        public boolean isValid() {
+            return (cached != OUT_OF_BOUNDS);
+        }
+    }
+
+    /*
+     *  gnu/regexp/CharIndexedReader.java
+     *  Copyright (C) 2001 Lee Sau Dan
+     *  Based on gnu.regexp.CharIndexedInputStream by Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    // TODO: move(x) shouldn't rely on calling next() x times
+
+    static class CharIndexedReader implements CharIndexed {
+        private static final int BUFFER_INCREMENT = 1024;
+        private static final int UNKNOWN = Integer.MAX_VALUE; // value for end
+    
+        private final BufferedReader br;
+        // so that we don't try to reset() right away
+        private int index = -1;
+
+        private int bufsize = BUFFER_INCREMENT;
+
+        private int end = UNKNOWN;
+
+        private char cached = OUT_OF_BOUNDS;
+
+        // Big enough for a \r\n pair
+        // lookBehind[0] = most recent
+        // lookBehind[1] = second most recent
+        private char[] lookBehind = new char[] { OUT_OF_BOUNDS, OUT_OF_BOUNDS }; 
+  
+        CharIndexedReader(Reader reader, int index) {
+            if (reader instanceof BufferedReader) {
+                br = (BufferedReader) reader; 
+            } else {
+                br = new BufferedReader(reader,BUFFER_INCREMENT);
+            }
+            next();
+            if (index > 0) move(index);
+        }
+    
+        private boolean next() {
+            lookBehind[1] = lookBehind[0];
+            lookBehind[0] = cached;
+
+            if (end == 1) {
+                cached = OUT_OF_BOUNDS;
+                return false;
+            }
+            end--; // closer to end
+       
+            try {
+                if (index != -1) {
+                    br.reset();
+                }
+                int i = br.read();
+                br.mark(bufsize);
+                if (i == -1) {
+                    end = 1;
+                    cached = OUT_OF_BOUNDS;
+                    return false;
+                }
+
+                // convert the byte read into a char
+                cached = (char) i;
+                index = 1;
+            } catch (IOException e) { 
+                e.printStackTrace();
+                cached = OUT_OF_BOUNDS;
+                return false; 
+            }
+            return true;
+        }
+    
+        public char charAt(int index) {
+            if (index == 0) {
+                return cached;
+            } else if (index >= end) {
+                return OUT_OF_BOUNDS;
+            } else if (index >= bufsize) {
+                // Allocate more space in the buffer.
+                try {
+                    while (bufsize <= index) bufsize += BUFFER_INCREMENT;
+                    br.reset();
+                    br.mark(bufsize);
+                    br.skip(index-1);
+                } catch (IOException e) { }
+            } else if (this.index != index) {
+                try {
+                    br.reset();
+                    br.skip(index-1);
+                } catch (IOException e) { }
+            } else if (index == -1) {
+                return lookBehind[0];
+            } else if (index == -2) {
+                return lookBehind[1];
+            } else if (index < -2) {
+                return OUT_OF_BOUNDS;
+            }
+
+            char ch = OUT_OF_BOUNDS;
+       
+            try {
+                int i = br.read();
+                this.index = index+1; // this.index is index of next pos relative to charAt(0)
+                if (i == -1) {
+                    // set flag that next should fail next time?
+                    end = index;
+                    return ch;
+                }
+                ch = (char) i;
+            } catch (IOException ie) { }
+       
+            return ch;
+        }
+    
+        public boolean move(int index) {
+            // move read position [index] clicks from 'charAt(0)'
+            boolean retval = true;
+            while (retval && (index-- > 0)) retval = next();
+            return retval;
+        }
+    
+        public boolean isValid() {
+            return (cached != OUT_OF_BOUNDS);
+        }
+    }
+    /*
+     *  gnu/regexp/CharIndexedString.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    static class CharIndexedString implements CharIndexed, Serializable {
+        private String s;
+        private int anchor;
+        private int len;
+    
+        CharIndexedString(String str, int index) {
+            s = str;
+            len = s.length();
+            anchor = index;
+        }
+
+        public char charAt(int index) {
+            int pos = anchor + index;
+            return ((pos < len) && (pos >= 0)) ? s.charAt(pos) : OUT_OF_BOUNDS;
+        }
+    
+        public boolean isValid() {
+            return (anchor < len);
+        }
+    
+        public boolean move(int index) {
+            return ((anchor += index) < len);
+        }
+    }
+    /*
+     *  gnu/regexp/CharIndexedStringBuffer.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    static class CharIndexedStringBuffer implements CharIndexed, Serializable {
+        private StringBuffer s;
+        private int anchor;
+
+        CharIndexedStringBuffer(StringBuffer str, int index) {
+            s = str;
+            anchor = index;
+        }
+
+        public char charAt(int index) {
+            int pos = anchor + index;
+            return ((pos < s.length()) && (pos >= 0)) ? s.charAt(pos) : OUT_OF_BOUNDS;
+        }
+
+        public boolean isValid() {
+            return (anchor < s.length());
+        }
+
+        public boolean move(int index) {
+            return ((anchor += index) < s.length());
+        }
+    }
+    /*
+     *  gnu/regexp/RE.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    static class IntPair implements Serializable {
+        public int first, second;
+    }
+
+    static class CharUnit implements Serializable {
+        public char ch;
+        public boolean bk;
+    }
+
+    /**
+     * RE provides the user interface for compiling and matching regular
+     * expressions.
+     * <P>
+     * A regular expression object (class RE) is compiled by constructing it
+     * from a String, StringBuffer or character array, with optional 
+     * compilation flags (below)
+     * and an optional syntax specification (see RESyntax; if not specified,
+     * <code>RESyntax.RE_SYNTAX_PERL5</code> is used).
+     * <P>
+     * Various methods attempt to match input text against a compiled
+     * regular expression.  These methods are:
+     * <LI><code>isMatch</code>: returns true if the input text in its entirety
+     * matches the regular expression pattern.
+     * <LI><code>getMatch</code>: returns the first match found in the input text,
+     * or null if no match is found.
+     * <LI><code>getAllMatches</code>: returns an array of all non-overlapping 
+     * matches found in the input text.  If no matches are found, the array is
+     * zero-length.
+     * <LI><code>substitute</code>: substitute the first occurence of the pattern
+     * in the input text with a replacement string (which may include
+     * metacharacters $0-$9, see REMatch.substituteInto).
+     * <LI><code>substituteAll</code>: same as above, but repeat for each match
+     * before returning.
+     * <LI><code>getMatchEnumeration</code>: returns an REMatchEnumeration object
+     * that allows iteration over the matches (see REMatchEnumeration for some
+     * reasons why you may want to do this instead of using <code>getAllMatches</code>.
+     * <P>
+     *
+     * These methods all have similar argument lists.  The input can be a
+     * String, a character array, a StringBuffer, a Reader or an
+     * InputStream of some sort.  Note that when using a Reader or
+     * InputStream, the stream read position cannot be guaranteed after
+     * attempting a match (this is not a bug, but a consequence of the way
+     * regular expressions work).  Using an REMatchEnumeration can
+     * eliminate most positioning problems.
+     *
+     * <P>
+     *
+     * The optional index argument specifies the offset from the beginning
+     * of the text at which the search should start (see the descriptions
+     * of some of the execution flags for how this can affect positional
+     * pattern operators).  For a Reader or InputStream, this means an
+     * offset from the current read position, so subsequent calls with the
+     * same index argument on a Reader or an InputStream will not
+     * necessarily access the same position on the stream, whereas
+     * repeated searches at a given index in a fixed string will return
+     * consistent results.
+     *
+     * <P>
+     * You can optionally affect the execution environment by using a
+     * combination of execution flags (constants listed below).
+     * 
+     * <P>
+     * All operations on a regular expression are performed in a
+     * thread-safe manner.
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     * @version 1.1.4-dev, to be released
+     */
+
+    public static class RE extends REToken {
+        // This String will be returned by getVersion()
+        private static final String VERSION = "1.1.4-dev";
+
+        // The localized strings are kept in a separate file
+        private static ResourceBundle messages = 
+            new ListResourceBundle() {
+                public Object[][] getContents() { return stuff; }
+                private Object[][] stuff = new Object[][] {
+                    { "error.prefix", "At position {0} in regular expression pattern:" },
+                    { "repeat.assertion", "repeated token is zero-width assertion" },
+                    { "repeat.chained", "attempted to repeat a token that is already repeated" },
+                    { "repeat.no.token", "quantifier (?*+{}) without preceding token" },
+                    { "repeat.empty.token", "repeated token may be empty" },
+                    { "unmatched.brace", "unmatched brace" },
+                    { "unmatched.bracket", "unmatched bracket" },
+                    { "unmatched.paren", "unmatched parenthesis" },
+                    { "interval.no.end", "expected end of interval" },
+                    { "class.no.end", "expected end of character class" },
+                    { "subexpr.no.end", "expected end of subexpression" },
+                    { "interval.order", "interval minimum is greater than maximum" },
+                    { "interval.error", "interval is empty or contains illegal chracters" },
+                    { "ends.with.backslash", "backslash at end of pattern" },
+                    { "syntax.final", "Syntax has been declared final and cannot be modified" } }; };
+
+        // These are, respectively, the first and last tokens in our linked list
+        // If there is only one token, firstToken == lastToken
+        private REToken firstToken, lastToken;
+
+        // This is the number of subexpressions in this regular expression,
+        // with a minimum value of zero.  Returned by getNumSubs()
+        private int numSubs;
+
+        /** Minimum length, in characters, of any possible match. */
+        private int minimumLength;
+
+        /**
+         * Compilation flag. Do  not  differentiate  case.   Subsequent
+         * searches  using  this  RE will be case insensitive.
+         */
+        public static final int REG_ICASE = 2;
+
+        /**
+         * Compilation flag. The match-any-character operator (dot)
+         * will match a newline character.  When set this overrides the syntax
+         * bit RE_DOT_NEWLINE (see RESyntax for details).  This is equivalent to
+         * the "/s" operator in Perl.
+         */
+        public static final int REG_DOT_NEWLINE = 4;
+
+        /**
+         * Compilation flag. Use multiline mode.  In this mode, the ^ and $
+         * anchors will match based on newlines within the input. This is
+         * equivalent to the "/m" operator in Perl.
+         */
+        public static final int REG_MULTILINE = 8;
+
+        /**
+         * Execution flag.
+         * The match-beginning operator (^) will not match at the beginning
+         * of the input string. Useful for matching on a substring when you
+         * know the context of the input is such that position zero of the
+         * input to the match test is not actually position zero of the text.
+         * <P>
+         * This example demonstrates the results of various ways of matching on
+         * a substring.
+         * <P>
+         * <CODE>
+         * String s = "food bar fool";<BR>
+         * RE exp = new RE("^foo.");<BR>
+         * REMatch m0 = exp.getMatch(s);<BR>
+         * REMatch m1 = exp.getMatch(s.substring(8));<BR>
+         * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR>
+         * REMatch m3 = exp.getMatch(s,8);                            <BR>
+         * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX);         <BR>
+         * <P>
+         * // Results:<BR>
+         * //  m0 = "food"<BR>
+         * //  m1 = "fool"<BR>
+         * //  m2 = null<BR>
+         * //  m3 = null<BR>
+         * //  m4 = "fool"<BR>
+         * </CODE>
+         */
+        public static final int REG_NOTBOL = 16;
+
+        /**
+         * Execution flag.
+         * The match-end operator ($) does not match at the end
+         * of the input string. Useful for matching on substrings.
+         */
+        public static final int REG_NOTEOL = 32;
+
+        /**
+         * Execution flag.
+         * When a match method is invoked that starts matching at a non-zero
+         * index into the input, treat the input as if it begins at the index
+         * given.  The effect of this flag is that the engine does not "see"
+         * any text in the input before the given index.  This is useful so
+         * that the match-beginning operator (^) matches not at position 0
+         * in the input string, but at the position the search started at
+         * (based on the index input given to the getMatch function).  See
+         * the example under REG_NOTBOL.  It also affects the use of the \&lt;
+         * and \b operators.
+         */
+        public static final int REG_ANCHORINDEX = 64;
+
+        /**
+         * Execution flag.
+         * The substitute and substituteAll methods will not attempt to
+         * interpolate occurrences of $1-$9 in the replacement text with
+         * the corresponding subexpressions.  For example, you may want to
+         * replace all matches of "one dollar" with "$1".
+         */
+        public static final int REG_NO_INTERPOLATE = 128;
+
+        /** Returns a string representing the version of the gnu.regexp package. */
+        public static final String version() {
+            return VERSION;
+        }
+
+        // Retrieves a message from the ResourceBundle
+        static final String getLocalizedMessage(String key) {
+            return messages.getString(key);
+        }
+
+        /**
+         * Constructs a regular expression pattern buffer without any compilation
+         * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5).
+         *
+         * @param pattern A regular expression pattern, in the form of a String,
+         *   StringBuffer or char[].  Other input types will be converted to
+         *   strings using the toString() method.
+         * @exception REException The input pattern could not be parsed.
+         * @exception NullPointerException The pattern was null.
+         */
+        public RE(Object pattern) throws REException {
+            this(pattern,0,RESyntax.RE_SYNTAX_PERL5,0,0);
+        }
+
+        /**
+         * Constructs a regular expression pattern buffer using the specified
+         * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5).
+         *
+         * @param pattern A regular expression pattern, in the form of a String,
+         *   StringBuffer, or char[].  Other input types will be converted to
+         *   strings using the toString() method.
+         * @param cflags The logical OR of any combination of the compilation flags listed above.
+         * @exception REException The input pattern could not be parsed.
+         * @exception NullPointerException The pattern was null.
+         */
+        public RE(Object pattern, int cflags) throws REException {
+            this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5,0,0);
+        }
+
+        /**
+         * Constructs a regular expression pattern buffer using the specified
+         * compilation flags and regular expression syntax.
+         *
+         * @param pattern A regular expression pattern, in the form of a String,
+         *   StringBuffer, or char[].  Other input types will be converted to
+         *   strings using the toString() method.
+         * @param cflags The logical OR of any combination of the compilation flags listed above.
+         * @param syntax The type of regular expression syntax to use.
+         * @exception REException The input pattern could not be parsed.
+         * @exception NullPointerException The pattern was null.
+         */
+        public RE(Object pattern, int cflags, RESyntax syntax) throws REException {
+            this(pattern,cflags,syntax,0,0);
+        }
+
+        // internal constructor used for alternation
+        private RE(REToken first, REToken last,int subs, int subIndex, int minLength) {
+            super(subIndex);
+            firstToken = first;
+            lastToken = last;
+            numSubs = subs;
+            minimumLength = minLength;
+            addToken(new RETokenEndSub(subIndex));
+        }
+
+        private RE(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException {
+            super(myIndex); // Subexpression index of this token.
+            initialize(patternObj, cflags, syntax, myIndex, nextSub);
+        }
+
+        // For use by subclasses
+        protected RE() { super(0); }
+
+        // The meat of construction
+        protected void initialize(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException {
+            char[] pattern;
+            if (patternObj instanceof String) {
+                pattern = ((String) patternObj).toCharArray();
+            } else if (patternObj instanceof char[]) {
+                pattern = (char[]) patternObj;
+            } else if (patternObj instanceof StringBuffer) {
+                pattern = new char [((StringBuffer) patternObj).length()];
+                ((StringBuffer) patternObj).getChars(0,pattern.length,pattern,0);
+            } else {
+                pattern = patternObj.toString().toCharArray();
+            }
+
+            int pLength = pattern.length;
+
+            numSubs = 0; // Number of subexpressions in this token.
+            Vector branches = null;
+
+            // linked list of tokens (sort of -- some closed loops can exist)
+            firstToken = lastToken = null;
+
+            // Precalculate these so we don't pay for the math every time we
+            // need to access them.
+            boolean insens = ((cflags & REG_ICASE) > 0);
+
+            // Parse pattern into tokens.  Does anyone know if it's more efficient
+            // to use char[] than a String.charAt()?  I'm assuming so.
+
+            // index tracks the position in the char array
+            int index = 0;
+
+            // this will be the current parse character (pattern[index])
+            CharUnit unit = new CharUnit();
+
+            // This is used for {x,y} calculations
+            IntPair minMax = new IntPair();
+
+            // Buffer a token so we can create a TokenRepeated, etc.
+            REToken currentToken = null;
+            char ch;
+
+            while (index < pLength) {
+                // read the next character unit (including backslash escapes)
+                index = getCharUnit(pattern,index,unit);
+
+                // ALTERNATION OPERATOR
+                //  \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT)
+                //  not available if RE_LIMITED_OPS is set
+
+                // TODO: the '\n' literal here should be a test against REToken.newline,
+                // which unfortunately may be more than a single character.
+                if ( ( (unit.ch == '|' && (syntax.get(RESyntax.RE_NO_BK_VBAR) ^ unit.bk))
+                       || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !unit.bk) )
+                     && !syntax.get(RESyntax.RE_LIMITED_OPS)) {
+                    // make everything up to here be a branch. create vector if nec.
+                    addToken(currentToken);
+                    RE theBranch = new RE(firstToken, lastToken, numSubs, subIndex, minimumLength);
+                    minimumLength = 0;
+                    if (branches == null) {
+                        branches = new Vector();
+                    }
+                    branches.addElement(theBranch);
+                    firstToken = lastToken = currentToken = null;
+                }
+      
+                // INTERVAL OPERATOR:
+                //  {x} | {x,} | {x,y}  (RE_INTERVALS && RE_NO_BK_BRACES)
+                //  \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES)
+                //
+                // OPEN QUESTION: 
+                //  what is proper interpretation of '{' at start of string?
+
+                else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) {
+                    int newIndex = getMinMax(pattern,index,minMax,syntax);
+                    if (newIndex > index) {
+                        if (minMax.first > minMax.second)
+                            throw new REException(getLocalizedMessage("interval.order"),REException.REG_BADRPT,newIndex);
+                        if (currentToken == null)
+                            throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,newIndex);
+                        if (currentToken instanceof RETokenRepeated) 
+                            throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,newIndex);
+                        if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
+                            throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,newIndex);
+                        if ((currentToken.getMinimumLength() == 0) && (minMax.second == Integer.MAX_VALUE))
+                            throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,newIndex);
+                        index = newIndex;
+                        currentToken = setRepeated(currentToken,minMax.first,minMax.second,index); 
+                    }
+                    else {
+                        addToken(currentToken);
+                        currentToken = new RETokenChar(subIndex,unit.ch,insens);
+                    } 
+                }
+      
+                // LIST OPERATOR:
+                //  [...] | [^...]
+
+                else if ((unit.ch == '[') && !unit.bk) {
+                    Vector options = new Vector();
+                    boolean negative = false;
+                    char lastChar = 0;
+                    if (index == pLength) throw new REException(getLocalizedMessage("unmatched.bracket"),REException.REG_EBRACK,index);
+       
+                    // Check for initial caret, negation
+                    if ((ch = pattern[index]) == '^') {
+                        negative = true;
+                        if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
+                        ch = pattern[index];
+                    }
+
+                    // Check for leading right bracket literal
+                    if (ch == ']') {
+                        lastChar = ch;
+                        if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
+                    }
+
+                    while ((ch = pattern[index++]) != ']') {
+                        if ((ch == '-') && (lastChar != 0)) {
+                            if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
+                            if ((ch = pattern[index]) == ']') {
+                                options.addElement(new RETokenChar(subIndex,lastChar,insens));
+                                lastChar = '-';
+                            } else {
+                                options.addElement(new RETokenRange(subIndex,lastChar,ch,insens));
+                                lastChar = 0;
+                                index++;
+                            }
+                        } else if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) {
+                            if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
+                            int posixID = -1;
+                            boolean negate = false;
+                            char asciiEsc = 0;
+                            if (("dswDSW".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) {
+                                switch (pattern[index]) {
+                                    case 'D':
+                                        negate = true;
+                                    case 'd':
+                                        posixID = RETokenPOSIX.DIGIT;
+                                        break;
+                                    case 'S':
+                                        negate = true;
+                                    case 's':
+                                        posixID = RETokenPOSIX.SPACE;
+                                        break;
+                                    case 'W':
+                                        negate = true;
+                                    case 'w':
+                                        posixID = RETokenPOSIX.ALNUM;
+                                        break;
+                                }
+                            }
+                            else if ("nrt".indexOf(pattern[index]) != -1) {
+                                switch (pattern[index]) {
+                                    case 'n':
+                                        asciiEsc = '\n';
+                                        break;
+                                    case 't':
+                                        asciiEsc = '\t';
+                                        break;
+                                    case 'r':
+                                        asciiEsc = '\r';
+                                        break;
+                                }
+                            }
+                            if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens));
+           
+                            if (posixID != -1) {
+                                options.addElement(new RETokenPOSIX(subIndex,posixID,insens,negate));
+                            } else if (asciiEsc != 0) {
+                                lastChar = asciiEsc;
+                            } else {
+                                lastChar = pattern[index];
+                            }
+                            ++index;
+                        } else if ((ch == '[') && (syntax.get(RESyntax.RE_CHAR_CLASSES)) && (index < pLength) && (pattern[index] == ':')) {
+                            StringBuffer posixSet = new StringBuffer();
+                            index = getPosixSet(pattern,index+1,posixSet);
+                            int posixId = RETokenPOSIX.intValue(posixSet.toString());
+                            if (posixId != -1)
+                                options.addElement(new RETokenPOSIX(subIndex,posixId,insens,false));
+                        } else {
+                            if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens));
+                            lastChar = ch;
+                        }
+                        if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
+                    } // while in list
+                    // Out of list, index is one past ']'
+           
+                    if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens));
+           
+                    // Create a new RETokenOneOf
+                    addToken(currentToken);
+                    options.trimToSize();
+                    currentToken = new RETokenOneOf(subIndex,options,negative);
+                }
+
+                // SUBEXPRESSIONS
+                //  (...) | \(...\) depending on RE_NO_BK_PARENS
+
+                else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) {
+                    boolean pure = false;
+                    boolean comment = false;
+                    boolean lookAhead = false;
+                    boolean negativelh = false;
+                    if ((index+1 < pLength) && (pattern[index] == '?')) {
+                        switch (pattern[index+1]) {
+                            case '!':
+                                if (syntax.get(RESyntax.RE_LOOKAHEAD)) {
+                                    pure = true;
+                                    negativelh = true;
+                                    lookAhead = true;
+                                    index += 2;
+                                }
+                                break;
+                            case '=':
+                                if (syntax.get(RESyntax.RE_LOOKAHEAD)) {
+                                    pure = true;
+                                    lookAhead = true;
+                                    index += 2;
+                                }
+                                break;
+                            case ':':
+                                if (syntax.get(RESyntax.RE_PURE_GROUPING)) {
+                                    pure = true;
+                                    index += 2;
+                                }
+                                break;
+                            case '#':
+                                if (syntax.get(RESyntax.RE_COMMENTS)) {
+                                    comment = true;
+                                }
+                                break;
+                            default:
+                                throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index);
+                        }
+                    }
+
+                    if (index >= pLength) {
+                        throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index);
+                    }
+
+                    // find end of subexpression
+                    int endIndex = index;
+                    int nextIndex = index;
+                    int nested = 0;
+
+                    while ( ((nextIndex = getCharUnit(pattern,endIndex,unit)) > 0)
+                            && !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) )
+                        if ((endIndex = nextIndex) >= pLength)
+                            throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex);
+                        else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))
+                            nested++;
+                        else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))
+                            nested--;
+
+                    // endIndex is now position at a ')','\)' 
+                    // nextIndex is end of string or position after ')' or '\)'
+
+                    if (comment) index = nextIndex;
+                    else { // not a comment
+                        // create RE subexpression as token.
+                        addToken(currentToken);
+                        if (!pure) {
+                            numSubs++;
+                        }
+
+                        int useIndex = (pure || lookAhead) ? 0 : nextSub + numSubs;
+                        currentToken = new RE(String.valueOf(pattern,index,endIndex-index).toCharArray(),cflags,syntax,useIndex,nextSub + numSubs);
+                        numSubs += ((RE) currentToken).getNumSubs();
+
+                        if (lookAhead) {
+                            currentToken = new RETokenLookAhead(currentToken,negativelh);
+                        }
+
+                        index = nextIndex;
+                    } // not a comment
+                } // subexpression
+    
+                // UNMATCHED RIGHT PAREN
+                // ) or \) throw exception if
+                // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD)
+                else if (!syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) && ((unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))) {
+                    throw new REException(getLocalizedMessage("unmatched.paren"),REException.REG_EPAREN,index);
+                }
+
+                // START OF LINE OPERATOR
+                //  ^
+
+                else if ((unit.ch == '^') && !unit.bk) {
+                    addToken(currentToken);
+                    currentToken = null;
+                    addToken(new RETokenStart(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null));
+                }
+
+                // END OF LINE OPERATOR
+                //  $
+
+                else if ((unit.ch == '$') && !unit.bk) {
+                    addToken(currentToken);
+                    currentToken = null;
+                    addToken(new RETokenEnd(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null));
+                }
+
+                // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null)
+                //  .
+
+                else if ((unit.ch == '.') && !unit.bk) {
+                    addToken(currentToken);
+                    currentToken = new RETokenAny(subIndex,syntax.get(RESyntax.RE_DOT_NEWLINE) || ((cflags & REG_DOT_NEWLINE) > 0),syntax.get(RESyntax.RE_DOT_NOT_NULL));
+                }
+
+                // ZERO-OR-MORE REPEAT OPERATOR
+                //  *
+
+                else if ((unit.ch == '*') && !unit.bk) {
+                    if (currentToken == null)
+                        throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
+                    if (currentToken instanceof RETokenRepeated)
+                        throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index);
+                    if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
+                        throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index);
+                    if (currentToken.getMinimumLength() == 0)
+                        throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,index);
+                    currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index);
+                }
+
+                // ONE-OR-MORE REPEAT OPERATOR
+                //  + | \+ depending on RE_BK_PLUS_QM
+                //  not available if RE_LIMITED_OPS is set
+
+                else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) {
+                    if (currentToken == null)
+                        throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
+                    if (currentToken instanceof RETokenRepeated)
+                        throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index);
+                    if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
+                        throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index);
+                    if (currentToken.getMinimumLength() == 0)
+                        throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,index);
+                    currentToken = setRepeated(currentToken,1,Integer.MAX_VALUE,index);
+                }
+
+                // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR
+                //  ? | \? depending on RE_BK_PLUS_QM
+                //  not available if RE_LIMITED_OPS is set
+                //  stingy matching if RE_STINGY_OPS is set and it follows a quantifier
+
+                else if ((unit.ch == '?') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) {
+                    if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
+
+                    // Check for stingy matching on RETokenRepeated
+                    if (currentToken instanceof RETokenRepeated) {
+                        if (syntax.get(RESyntax.RE_STINGY_OPS) && !((RETokenRepeated)currentToken).isStingy())
+                            ((RETokenRepeated)currentToken).makeStingy();
+                        else
+                            throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index);
+                    }
+                    else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
+                        throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index);
+                    else
+                        currentToken = setRepeated(currentToken,0,1,index);
+                }
+       
+                // BACKREFERENCE OPERATOR
+                //  \1 \2 ... \9
+                // not available if RE_NO_BK_REFS is set
+
+                else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenBackRef(subIndex,Character.digit(unit.ch,10),insens);
+                }
+
+                // START OF STRING OPERATOR
+                //  \A if RE_STRING_ANCHORS is set
+      
+                else if (unit.bk && (unit.ch == 'A') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenStart(subIndex,null);
+                }
+
+                // WORD BREAK OPERATOR
+                //  \b if ????
+
+                else if (unit.bk && (unit.ch == 'b') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, false);
+                } 
+
+                // WORD BEGIN OPERATOR 
+                //  \< if ????
+                else if (unit.bk && (unit.ch == '<')) {
+                    addToken(currentToken);
+                    currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN, false);
+                } 
+
+                // WORD END OPERATOR 
+                //  \> if ????
+                else if (unit.bk && (unit.ch == '>')) {
+                    addToken(currentToken);
+                    currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.END, false);
+                } 
+
+                // NON-WORD BREAK OPERATOR
+                // \B if ????
+
+                else if (unit.bk && (unit.ch == 'B') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, true);
+                } 
+
+      
+                // DIGIT OPERATOR
+                //  \d if RE_CHAR_CLASS_ESCAPES is set
+      
+                else if (unit.bk && (unit.ch == 'd') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,false);
+                }
+
+                // NON-DIGIT OPERATOR
+                //  \D
+
+                else if (unit.bk && (unit.ch == 'D') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,true);
+                }
+
+                // NEWLINE ESCAPE
+                //  \n
+
+                else if (unit.bk && (unit.ch == 'n')) {
+                    addToken(currentToken);
+                    currentToken = new RETokenChar(subIndex,'\n',false);
+                }
+
+                // RETURN ESCAPE
+                //  \r
+
+                else if (unit.bk && (unit.ch == 'r')) {
+                    addToken(currentToken);
+                    currentToken = new RETokenChar(subIndex,'\r',false);
+                }
+
+                // WHITESPACE OPERATOR
+                //  \s if RE_CHAR_CLASS_ESCAPES is set
+
+                else if (unit.bk && (unit.ch == 's') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,false);
+                }
+
+                // NON-WHITESPACE OPERATOR
+                //  \S
+
+                else if (unit.bk && (unit.ch == 'S') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,true);
+                }
+
+                // TAB ESCAPE
+                //  \t
+
+                else if (unit.bk && (unit.ch == 't')) {
+                    addToken(currentToken);
+                    currentToken = new RETokenChar(subIndex,'\t',false);
+                }
+
+                // ALPHANUMERIC OPERATOR
+                //  \w
+
+                else if (unit.bk && (unit.ch == 'w') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,false);
+                }
+
+                // NON-ALPHANUMERIC OPERATOR
+                //  \W
+
+                else if (unit.bk && (unit.ch == 'W') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,true);
+                }
+
+                // END OF STRING OPERATOR
+                //  \Z
+
+                else if (unit.bk && (unit.ch == 'Z') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
+                    addToken(currentToken);
+                    currentToken = new RETokenEnd(subIndex,null);
+                }
+
+                // NON-SPECIAL CHARACTER (or escape to make literal)
+                //  c | \* for example
+
+                else {  // not a special character
+                    addToken(currentToken);
+                    currentToken = new RETokenChar(subIndex,unit.ch,insens);
+                } 
+            } // end while
+
+            // Add final buffered token and an EndSub marker
+            addToken(currentToken);
+      
+            if (branches != null) {
+                branches.addElement(new RE(firstToken,lastToken,numSubs,subIndex,minimumLength));
+                branches.trimToSize(); // compact the Vector
+                minimumLength = 0;
+                firstToken = lastToken = null;
+                addToken(new RETokenOneOf(subIndex,branches,false));
+            } 
+            else addToken(new RETokenEndSub(subIndex));
+
+        }
+
+        private static int getCharUnit(char[] input, int index, CharUnit unit) throws REException {
+            unit.ch = input[index++];
+            if (unit.bk = (unit.ch == '\\'))
+                if (index < input.length)
+                    unit.ch = input[index++];
+                else throw new REException(getLocalizedMessage("ends.with.backslash"),REException.REG_ESCAPE,index);
+            return index;
+        }
+
+        /**
+         * Checks if the regular expression matches the input in its entirety.
+         *
+         * @param input The input text.
+         */
+        public boolean isMatch(Object input) {
+            return isMatch(input,0,0);
+        }
+  
+        /**
+         * Checks if the input string, starting from index, is an exact match of
+         * this regular expression.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         */
+        public boolean isMatch(Object input,int index) {
+            return isMatch(input,index,0);
+        }
+  
+
+        /**
+         * Checks if the input, starting from index and using the specified
+         * execution flags, is an exact match of this regular expression.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         */
+        public boolean isMatch(Object input,int index,int eflags) {
+            return isMatchImpl(makeCharIndexed(input,index),index,eflags);
+        }
+
+        private boolean isMatchImpl(CharIndexed input, int index, int eflags) {
+            if (firstToken == null)  // Trivial case
+                return (input.charAt(0) == CharIndexed.OUT_OF_BOUNDS);
+            REMatch m = new REMatch(numSubs, index, eflags);
+            if (firstToken.match(input, m)) {
+                while (m != null) {
+                    if (input.charAt(m.index) == CharIndexed.OUT_OF_BOUNDS) {
+                        return true;
+                    }
+                    m = m.next;
+                }
+            }
+            return false;
+        }
+    
+        /**
+         * Returns the maximum number of subexpressions in this regular expression.
+         * If the expression contains branches, the value returned will be the
+         * maximum subexpressions in any of the branches.
+         */
+        public int getNumSubs() {
+            return numSubs;
+        }
+
+        // Overrides REToken.setUncle
+        void setUncle(REToken uncle) {
+            if (lastToken != null) {
+                lastToken.setUncle(uncle);
+            } else super.setUncle(uncle); // to deal with empty subexpressions
+        }
+
+        // Overrides REToken.chain
+
+        boolean chain(REToken next) {
+            super.chain(next);
+            setUncle(next);
+            return true;
+        }
+
+        /**
+         * Returns the minimum number of characters that could possibly
+         * constitute a match of this regular expression.
+         */
+        public int getMinimumLength() {
+            return minimumLength;
+        }
+
+        /**
+         * Returns an array of all matches found in the input.
+         *
+         * If the regular expression allows the empty string to match, it will
+         * substitute matches at all positions except the end of the input.
+         *
+         * @param input The input text.
+         * @return a non-null (but possibly zero-length) array of matches
+         */
+        public REMatch[] getAllMatches(Object input) {
+            return getAllMatches(input,0,0);
+        }
+
+        /**
+         * Returns an array of all matches found in the input,
+         * beginning at the specified index position.
+         *
+         * If the regular expression allows the empty string to match, it will
+         * substitute matches at all positions except the end of the input.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @return a non-null (but possibly zero-length) array of matches
+         */
+        public REMatch[] getAllMatches(Object input, int index) {
+            return getAllMatches(input,index,0);
+        }
+
+        /**
+         * Returns an array of all matches found in the input string,
+         * beginning at the specified index position and using the specified
+         * execution flags.
+         *
+         * If the regular expression allows the empty string to match, it will
+         * substitute matches at all positions except the end of the input.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         * @return a non-null (but possibly zero-length) array of matches
+         */
+        public REMatch[] getAllMatches(Object input, int index, int eflags) {
+            return getAllMatchesImpl(makeCharIndexed(input,index),index,eflags);
+        }
+
+        // this has been changed since 1.03 to be non-overlapping matches
+        private REMatch[] getAllMatchesImpl(CharIndexed input, int index, int eflags) {
+            Vector all = new Vector();
+            REMatch m = null;
+            while ((m = getMatchImpl(input,index,eflags,null)) != null) {
+                all.addElement(m);
+                index = m.getEndIndex();
+                if (m.end[0] == 0) {   // handle pathological case of zero-length match
+                    index++;
+                    input.move(1);
+                } else {
+                    input.move(m.end[0]);
+                }
+                if (!input.isValid()) break;
+            }
+            REMatch[] mset = new REMatch[all.size()];
+            all.copyInto(mset);
+            return mset;
+        }
+  
+        /* Implements abstract method REToken.match() */
+        boolean match(CharIndexed input, REMatch mymatch) { 
+            if (firstToken == null) return next(input, mymatch);
+
+            // Note the start of this subexpression
+            mymatch.start[subIndex] = mymatch.index;
+
+            return firstToken.match(input, mymatch);
+        }
+  
+        /**
+         * Returns the first match found in the input.  If no match is found,
+         * null is returned.
+         *
+         * @param input The input text.
+         * @return An REMatch instance referencing the match, or null if none.
+         */
+        public REMatch getMatch(Object input) {
+            return getMatch(input,0,0);
+        }
+  
+        /**
+         * Returns the first match found in the input, beginning
+         * the search at the specified index.  If no match is found,
+         * returns null.
+         *
+         * @param input The input text.
+         * @param index The offset within the text to begin looking for a match.
+         * @return An REMatch instance referencing the match, or null if none.
+         */
+        public REMatch getMatch(Object input, int index) {
+            return getMatch(input,index,0);
+        }
+  
+        /**
+         * Returns the first match found in the input, beginning
+         * the search at the specified index, and using the specified
+         * execution flags.  If no match is found, returns null.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         * @return An REMatch instance referencing the match, or null if none.
+         */
+        public REMatch getMatch(Object input, int index, int eflags) {
+            return getMatch(input,index,eflags,null);
+        }
+
+        /**
+         * Returns the first match found in the input, beginning the search
+         * at the specified index, and using the specified execution flags.
+         * If no match is found, returns null.  If a StringBuffer is
+         * provided and is non-null, the contents of the input text from the
+         * index to the beginning of the match (or to the end of the input,
+         * if there is no match) are appended to the StringBuffer.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         * @param buffer The StringBuffer to save pre-match text in.
+         * @return An REMatch instance referencing the match, or null if none.  */
+        public REMatch getMatch(Object input, int index, int eflags, StringBuffer buffer) {
+            return getMatchImpl(makeCharIndexed(input,index),index,eflags,buffer);
+        }
+
+        REMatch getMatchImpl(CharIndexed input, int anchor, int eflags, StringBuffer buffer) {
+            // Create a new REMatch to hold results
+            REMatch mymatch = new REMatch(numSubs, anchor, eflags);
+            do {
+                // Optimization: check if anchor + minimumLength > length
+                if (minimumLength == 0 || input.charAt(minimumLength-1) != CharIndexed.OUT_OF_BOUNDS) {
+                    if (match(input, mymatch)) {
+                        // Find longest match of them all to observe leftmost longest
+                        REMatch longest = mymatch;
+                        while ((mymatch = mymatch.next) != null) {
+                            if (mymatch.index > longest.index) {
+                                longest = mymatch;
+                            }
+                        }
+                 
+                        longest.end[0] = longest.index;
+                        longest.finish(input);
+                        return longest;
+                    }
+                }
+                mymatch.clear(++anchor);
+                // Append character to buffer if needed
+                if (buffer != null && input.charAt(0) != CharIndexed.OUT_OF_BOUNDS) {
+                    buffer.append(input.charAt(0));
+                }
+            } while (input.move(1));
+      
+            return null;
+        }
+
+        /**
+         * Returns an REMatchEnumeration that can be used to iterate over the
+         * matches found in the input text.
+         *
+         * @param input The input text.
+         * @return A non-null REMatchEnumeration instance.
+         */
+        public REMatchEnumeration getMatchEnumeration(Object input) {
+            return getMatchEnumeration(input,0,0);
+        }
+
+
+        /**
+         * Returns an REMatchEnumeration that can be used to iterate over the
+         * matches found in the input text.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @return A non-null REMatchEnumeration instance, with its input cursor
+         *  set to the index position specified.
+         */
+        public REMatchEnumeration getMatchEnumeration(Object input, int index) {
+            return getMatchEnumeration(input,index,0);
+        }
+
+        /**
+         * Returns an REMatchEnumeration that can be used to iterate over the
+         * matches found in the input text.
+         *
+         * @param input The input text.
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         * @return A non-null REMatchEnumeration instance, with its input cursor
+         *  set to the index position specified.
+         */
+        public REMatchEnumeration getMatchEnumeration(Object input, int index, int eflags) {
+            return new REMatchEnumeration(this,makeCharIndexed(input,index),index,eflags);
+        }
+
+
+        /**
+         * Substitutes the replacement text for the first match found in the input.
+         *
+         * @param input The input text.
+         * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+         * @return A String interpolating the substituted text.
+         * @see REMatch#substituteInto
+         */
+        public String substitute(Object input,String replace) {
+            return substitute(input,replace,0,0);
+        }
+
+        /**
+         * Substitutes the replacement text for the first match found in the input
+         * beginning at the specified index position.  Specifying an index
+         * effectively causes the regular expression engine to throw away the
+         * specified number of characters. 
+         *
+         * @param input The input text.
+         * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+         * @param index The offset index at which the search should be begin.
+         * @return A String containing the substring of the input, starting
+         *   at the index position, and interpolating the substituted text.
+         * @see REMatch#substituteInto
+         */
+        public String substitute(Object input,String replace,int index) {
+            return substitute(input,replace,index,0);
+        }
+
+        /**
+         * Substitutes the replacement text for the first match found in the input
+         * string, beginning at the specified index position and using the
+         * specified execution flags.
+         *
+         * @param input The input text.
+         * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         * @return A String containing the substring of the input, starting
+         *   at the index position, and interpolating the substituted text.
+         * @see REMatch#substituteInto
+         */
+        public String substitute(Object input,String replace,int index,int eflags) {
+            return substituteImpl(makeCharIndexed(input,index),replace,index,eflags);
+        }
+
+        private String substituteImpl(CharIndexed input,String replace,int index,int eflags) {
+            StringBuffer buffer = new StringBuffer();
+            REMatch m = getMatchImpl(input,index,eflags,buffer);
+            if (m==null) return buffer.toString();
+            buffer.append( ((eflags & REG_NO_INTERPOLATE) > 0) ?
+                           replace : m.substituteInto(replace) );
+            if (input.move(m.end[0])) {
+                do {
+                    buffer.append(input.charAt(0));
+                } while (input.move(1));
+            }
+            return buffer.toString();
+        }
+  
+        /**
+         * Substitutes the replacement text for each non-overlapping match found 
+         * in the input text.
+         *
+         * @param input The input text.
+         * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+         * @return A String interpolating the substituted text.
+         * @see REMatch#substituteInto
+         */
+        public String substituteAll(Object input,String replace) {
+            return substituteAll(input,replace,0,0);
+        }
+
+        /**
+         * Substitutes the replacement text for each non-overlapping match found 
+         * in the input text, starting at the specified index.
+         *
+         * If the regular expression allows the empty string to match, it will
+         * substitute matches at all positions except the end of the input.
+         *
+         * @param input The input text.
+         * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+         * @param index The offset index at which the search should be begin.
+         * @return A String containing the substring of the input, starting
+         *   at the index position, and interpolating the substituted text.
+         * @see REMatch#substituteInto
+         */
+        public String substituteAll(Object input,String replace,int index) {
+            return substituteAll(input,replace,index,0);
+        }
+        /**
+         * Substitutes the replacement text for each non-overlapping match found 
+         * in the input text, starting at the specified index and using the
+         * specified execution flags.
+         *
+         * @param input The input text.
+         * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+         * @param index The offset index at which the search should be begin.
+         * @param eflags The logical OR of any execution flags above.
+         * @return A String containing the substring of the input, starting
+         *   at the index position, and interpolating the substituted text.
+         * @see REMatch#substituteInto
+         */
+        public String substituteAll(Object input,String replace,int index,int eflags) {
+            return substituteAllImpl(makeCharIndexed(input,index),replace,index,eflags);
+        }
+
+        private String substituteAllImpl(CharIndexed input,String replace,int index,int eflags) {
+            StringBuffer buffer = new StringBuffer();
+            REMatch m;
+            while ((m = getMatchImpl(input,index,eflags,buffer)) != null) {
+                buffer.append( ((eflags & REG_NO_INTERPOLATE) > 0) ?
+                               replace : m.substituteInto(replace) );
+                index = m.getEndIndex();
+                if (m.end[0] == 0) {
+                    char ch = input.charAt(0);
+                    if (ch != CharIndexed.OUT_OF_BOUNDS) 
+                        buffer.append(ch);
+                    input.move(1);
+                } else {
+                    input.move(m.end[0]);
+                }
+
+                if (!input.isValid()) break;
+            }
+            return buffer.toString();
+        }
+  
+        /* Helper function for constructor */
+        private void addToken(REToken next) {
+            if (next == null) return;
+            minimumLength += next.getMinimumLength();
+            if (firstToken == null) {
+                lastToken = firstToken = next;
+            } else {
+                // if chain returns false, it "rejected" the token due to
+                // an optimization, and next was combined with lastToken
+                if (lastToken.chain(next)) {
+                    lastToken = next;
+                }
+            }
+        }
+
+        private static REToken setRepeated(REToken current, int min, int max, int index) throws REException {
+            if (current == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
+            return new RETokenRepeated(current.subIndex,current,min,max);
+        }
+
+        private static int getPosixSet(char[] pattern,int index,StringBuffer buf) {
+            // Precondition: pattern[index-1] == ':'
+            // we will return pos of closing ']'.
+            int i;
+            for (i=index; i<(pattern.length-1); i++) {
+                if ((pattern[i] == ':') && (pattern[i+1] == ']'))
+                    return i+2;
+                buf.append(pattern[i]);
+            }
+            return index; // didn't match up
+        }
+
+        private int getMinMax(char[] input,int index,IntPair minMax,RESyntax syntax) throws REException {
+            // Precondition: input[index-1] == '{', minMax != null
+
+            boolean mustMatch = !syntax.get(RESyntax.RE_NO_BK_BRACES);
+            int startIndex = index;
+            if (index == input.length) {
+                if (mustMatch)
+                    throw new REException(getLocalizedMessage("unmatched.brace"),REException.REG_EBRACE,index);
+                else
+                    return startIndex;
+            }
+    
+            int min,max=0;
+            CharUnit unit = new CharUnit();
+            StringBuffer buf = new StringBuffer();
+    
+            // Read string of digits
+            do {
+                index = getCharUnit(input,index,unit);
+                if (Character.isDigit(unit.ch))
+                    buf.append(unit.ch);
+            } while ((index != input.length) && Character.isDigit(unit.ch));
+
+            // Check for {} tomfoolery
+            if (buf.length() == 0) {
+                if (mustMatch)
+                    throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index);
+                else
+                    return startIndex;
+            }
+
+            min = Integer.parseInt(buf.toString());
+       
+            if ((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk))
+                max = min;
+            else if (index == input.length)
+                if (mustMatch)
+                    throw new REException(getLocalizedMessage("interval.no.end"),REException.REG_EBRACE,index);
+                else
+                    return startIndex;
+            else if ((unit.ch == ',') && !unit.bk) {
+                buf = new StringBuffer();
+                // Read string of digits
+                while (((index = getCharUnit(input,index,unit)) != input.length) && Character.isDigit(unit.ch))
+                    buf.append(unit.ch);
+
+                if (!((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)))
+                    if (mustMatch)
+                        throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index);
+                    else
+                        return startIndex;
+
+                // This is the case of {x,}
+                if (buf.length() == 0) max = Integer.MAX_VALUE;
+                else max = Integer.parseInt(buf.toString());
+            } else
+                if (mustMatch)
+                    throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index);
+                else
+                    return startIndex;
+
+            // We know min and max now, and they are valid.
+
+            minMax.first = min;
+            minMax.second = max;
+
+            // return the index following the '}'
+            return index;
+        }
+
+        /**
+         * Return a human readable form of the compiled regular expression,
+         * useful for debugging.
+         */
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            dump(sb);
+            return sb.toString();
+        }
+
+        void dump(StringBuffer os) {
+            os.append('(');
+            if (subIndex == 0)
+                os.append("?:");
+            if (firstToken != null)
+                firstToken.dumpAll(os);
+            os.append(')');
+        }
+
+        // Cast input appropriately or throw exception
+        private static CharIndexed makeCharIndexed(Object input, int index) {
+            // We could let a String fall through to final input, but since
+            // it's the most likely input type, we check it first.
+            if (input instanceof String)
+                return new CharIndexedString((String) input,index);
+            else if (input instanceof char[])
+                return new CharIndexedCharArray((char[]) input,index);
+            else if (input instanceof StringBuffer)
+                return new CharIndexedStringBuffer((StringBuffer) input,index);
+            else if (input instanceof InputStream)
+                return new CharIndexedInputStream((InputStream) input,index);
+            else if (input instanceof Reader)
+                return new CharIndexedReader((Reader) input, index);
+            else if (input instanceof CharIndexed)
+                return (CharIndexed) input; // do we lose index info?
+            else 
+                return new CharIndexedString(input.toString(), index);
+        }
+    }
+    /*
+     *  gnu/regexp/REException.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+
+    /**
+     * This is the regular expression exception class.  An exception of this type
+     * defines the three attributes:
+     * <OL>
+     * <LI> A descriptive message of the error.
+     * <LI> An integral type code equivalent to one of the statically
+     *      defined symbols listed below.
+     * <LI> The approximate position in the input string where the error
+     *      occurred.
+     * </OL>
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     */
+
+    public static class REException extends Exception {
+        private int type;
+        private int pos;
+
+        // Error conditions from GNU regcomp(3) manual
+
+        /**
+         * Error flag.
+         * Invalid use of repetition operators such  as  using
+         * `*' as the first character.
+         */
+        public static final int REG_BADRPT  =  1;
+
+        /**
+         * Error flag.
+         * Invalid use of back reference operator.
+         */
+        public static final int REG_BADBR   =  2;
+
+        /**
+         * Error flag.
+         * Un-matched brace interval operators.
+         */
+        public static final int REG_EBRACE  =  3;
+
+        /**
+         * Error flag.
+         * Un-matched bracket list operators.
+         */
+        public static final int REG_EBRACK  =  4;
+
+        /**
+         * Error flag.
+         * Invalid  use  of the range operator, eg. the ending
+         * point of the range occurs  prior  to  the  starting
+         * point.
+         */
+        public static final int REG_ERANGE  =  5;
+
+        /**
+         * Error flag.
+         * Unknown character class name. <B>Not implemented</B>.
+         */
+        public static final int REG_ECTYPE  =  6;
+
+        /**
+         * Error flag.
+         * Un-matched parenthesis group operators.
+         */
+        public static final int REG_EPAREN  =  7;
+
+        /**
+         * Error flag.
+         * Invalid back reference to a subexpression.
+         */
+        public static final int REG_ESUBREG =  8;
+
+        /**
+         * Error flag.
+         * Non specific error. <B>Not implemented</B>.
+         */
+        public static final int REG_EEND    =  9;
+
+        /**
+         * Error flag.
+         * Invalid escape sequence. <B>Not implemented</B>.
+         */
+        public static final int REG_ESCAPE  = 10;
+
+        /**
+         * Error flag.
+         * Invalid  use  of pattern operators such as group or list.
+         */
+        public static final int REG_BADPAT  = 11;
+
+        /**
+         * Error flag.
+         * Compiled  regular  expression  requires  a  pattern
+         * buffer larger than 64Kb. <B>Not implemented</B>.
+         */
+        public static final int REG_ESIZE   = 12;
+
+        /**
+         * Error flag.
+         * The regex routines ran out of memory. <B>Not implemented</B>.
+         */
+        public static final int REG_ESPACE  = 13;
+
+        REException(String msg, int type, int position) { 
+            super(msg); 
+            this.type = type;
+            this.pos = position;
+        }
+
+        /**
+         * Returns the type of the exception, one of the constants listed above.
+         */
+
+        public int getType() {
+            return type;
+        }
+
+        /**
+         * Returns the position, relative to the string or character array being
+         * compiled, where the error occurred.  This position is generally the point
+         * where the error was detected, not necessarily the starting index of
+         * a bad subexpression.
+         */
+        public int getPosition() {
+            return pos;
+        }
+
+        /**
+         * Reports the descriptive message associated with this exception
+         * as well as its index position in the string or character array
+         * being compiled.
+         */
+        public String getMessage() {
+            Object[] args = {new Integer(pos)};
+            StringBuffer sb = new StringBuffer();
+            String prefix = RE.getLocalizedMessage("error.prefix");
+            sb.append(MessageFormat.format(prefix, args));
+            sb.append('\n');
+            sb.append(super.getMessage());
+            return sb.toString();
+        }
+    }
+    /*
+     *  gnu/regexp/REFilterInputStream.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    /**
+     * Replaces instances of a given RE found within an InputStream
+     * with replacement text.   The replacements are interpolated into the
+     * stream when a match is found.
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     * @deprecated This class cannot properly handle all character
+     *             encodings.  For proper handling, use the REFilterReader
+     *             class instead.
+     */
+
+    public static class REFilterInputStream extends FilterInputStream {
+
+        private RE expr;
+        private String replace;
+        private String buffer;
+        private int bufpos;
+        private int offset;
+        private CharIndexedInputStream stream;
+
+        /**
+         * Creates an REFilterInputStream.  When reading from this stream,
+         * occurrences of patterns matching the supplied regular expression
+         * will be replaced with the supplied replacement text (the
+         * metacharacters $0 through $9 may be used to refer to the full
+         * match or subexpression matches).
+         *
+         * @param stream The InputStream to be filtered.
+         * @param expr The regular expression to search for.
+         * @param replace The text pattern to replace matches with.  
+         */
+        public REFilterInputStream(InputStream stream, RE expr, String replace) {
+            super(stream);
+            this.stream = new CharIndexedInputStream(stream,0);
+            this.expr = expr;
+            this.replace = replace;
+        }
+
+        /**
+         * Reads the next byte from the stream per the general contract of
+         * InputStream.read().  Returns -1 on error or end of stream.
+         */
+        public int read() {
+            // If we have buffered replace data, use it.
+            if ((buffer != null) && (bufpos < buffer.length())) {
+                return (int) buffer.charAt(bufpos++);
+            }
+
+            // check if input is at a valid position
+            if (!stream.isValid()) return -1;
+
+            REMatch mymatch = new REMatch(expr.getNumSubs(),offset,0);
+            if (expr.match(stream, mymatch)) {
+                mymatch.end[0] = mymatch.index;
+                mymatch.finish(stream);
+                stream.move(mymatch.toString().length());
+                offset += mymatch.toString().length();
+                buffer = mymatch.substituteInto(replace);
+                bufpos = 1;
+
+                // This is prone to infinite loops if replace string turns out empty.
+                if (buffer.length() > 0) {
+                    return buffer.charAt(0);
+                }
+            }
+            char ch = stream.charAt(0);
+            if (ch == CharIndexed.OUT_OF_BOUNDS) return -1;
+            stream.move(1);
+            offset++;
+            return ch;
+        }
+
+        /** 
+         * Returns false.  REFilterInputStream does not support mark() and
+         * reset() methods. 
+         */
+        public boolean markSupported() {
+            return false;
+        }
+
+        /** Reads from the stream into the provided array. */
+        public int read(byte[] b, int off, int len) {
+            int i;
+            int ok = 0;
+            while (len-- > 0) {
+                i = read();
+                if (i == -1) return (ok == 0) ? -1 : ok;
+                b[off++] = (byte) i;
+                ok++;
+            }
+            return ok;
+        }
+
+        /** Reads from the stream into the provided array. */
+        public int read(byte[] b) {
+            return read(b,0,b.length);
+        }
+    }
+    /*
+     *  gnu/regexp/REFilterReader.java
+     *  Copyright (C) 2001 Lee Sau Dan
+     *  Based on gnu.regexp.REFilterInputStream by Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    /**
+     * Replaces instances of a given RE with replacement text. 
+     *
+     * @author <A HREF="http://www.csis.hku.hk/~sdlee/">Lee Sau Dan</A>
+     * @since gnu.regexp 1.1.0
+     */
+
+    public static class REFilterReader extends FilterReader {
+
+        private RE expr;
+        private String replace;
+        private String buffer;
+        private int bufpos;
+        private int offset;
+        private CharIndexedReader stream;
+
+        /**
+         * Creates an REFilterReader.  When reading from this stream,
+         * occurrences of patterns matching the supplied regular expression
+         * will be replaced with the supplied replacement text (the
+         * metacharacters $0 through $9 may be used to refer to the full
+         * match or subexpression matches.
+         *
+         * @param stream The Reader to be filtered.
+         * @param expr The regular expression to search for.
+         * @param replace The text pattern to replace matches with.  
+         */
+        public REFilterReader(Reader stream, RE expr, String replace) {
+            super(stream);
+            this.stream = new CharIndexedReader(stream,0);
+            this.expr = expr;
+            this.replace = replace;
+        }
+
+        /**
+         * Reads the next character from the stream per the general contract of
+         * Reader.read().  Returns -1 on error or end of stream.
+         */
+        public int read() {
+            // If we have buffered replace data, use it.
+            if ((buffer != null) && (bufpos < buffer.length())) {
+                return (int) buffer.charAt(bufpos++);
+            }
+
+            // check if input is at a valid position
+            if (!stream.isValid()) return -1;
+
+            REMatch mymatch = new REMatch(expr.getNumSubs(),offset,0);
+            if (expr.match(stream,mymatch)) {
+                mymatch.end[0] = mymatch.index;
+                mymatch.finish(stream);
+                stream.move(mymatch.toString().length());
+                offset += mymatch.toString().length();
+                buffer = mymatch.substituteInto(replace);
+                bufpos = 1;
+
+                if (buffer.length() > 0) {
+                    return buffer.charAt(0);
+                }
+            }
+            char ch = stream.charAt(0);
+            if (ch == CharIndexed.OUT_OF_BOUNDS) return -1;
+            stream.move(1);
+            offset++;
+            return ch;
+        }
+
+        /** 
+         * Returns false.  REFilterReader does not support mark() and
+         * reset() methods. 
+         */
+        public boolean markSupported() {
+            return false;
+        }
+
+        /** Reads from the stream into the provided array. */
+        public int read(char[] b, int off, int len) {
+            int i;
+            int ok = 0;
+            while (len-- > 0) {
+                i = read();
+                if (i == -1) return (ok == 0) ? -1 : ok;
+                b[off++] = (char) i;
+                ok++;
+            }
+            return ok;
+        }
+
+        /** Reads from the stream into the provided array. */
+        public int read(char[] b) {
+            return read(b,0,b.length);
+        }
+    }
+    /*
+     *  gnu/regexp/REMatch.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    /**
+     * An instance of this class represents a match
+     * completed by a gnu.regexp matching function. It can be used
+     * to obtain relevant information about the location of a match
+     * or submatch.
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     */
+    public static final class REMatch implements Serializable, Cloneable {
+        private String matchedText;
+
+        // These variables are package scope for fast access within the engine
+        int eflags; // execution flags this match was made using
+
+        // Offset in source text where match was tried.  This is zero-based;
+        // the actual position in the source text is given by (offset + anchor).
+        int offset;
+
+        // Anchor position refers to the index into the source input
+        // at which the matching operation began.
+        // This is also useful for the ANCHORINDEX option.
+        int anchor;
+
+        // Package scope; used by RE.
+        int index; // used while matching to mark current match position in input
+        int[] start; // start positions (relative to offset) for each (sub)exp.
+        int[] end;   // end positions for the same
+        REMatch next; // other possibility (to avoid having to use arrays)
+
+        public Object clone() {
+            try {
+                REMatch copy = (REMatch) super.clone();
+                copy.next = null;
+
+                copy.start = (int[]) start.clone();
+                copy.end = (int[]) end.clone();
+
+                return copy;
+            } catch (CloneNotSupportedException e) {
+                throw new Error(); // doesn't happen
+            }
+        }
+
+        void assignFrom(REMatch other) {
+            start = other.start;
+            end = other.end;
+            index = other.index;
+            // need to deep clone?
+            next = other.next;
+        }
+
+        REMatch(int subs, int anchor, int eflags) {
+            start = new int[subs+1];
+            end = new int[subs+1];
+            this.anchor = anchor;
+            this.eflags = eflags;
+            clear(anchor);
+        }
+
+        void finish(CharIndexed text) {
+            start[0] = 0;
+            StringBuffer sb = new StringBuffer();
+            int i;
+            for (i = 0; i < end[0]; i++)
+                sb.append(text.charAt(i));
+            matchedText = sb.toString();
+            for (i = 0; i < start.length; i++) {
+                // If any subexpressions didn't terminate, they don't count
+                // TODO check if this code ever gets hit
+                if ((start[i] == -1) ^ (end[i] == -1)) {
+                    start[i] = -1;
+                    end[i] = -1;
+                }
+            }
+            next = null; // cut off alternates
+        }
+    
+        /** Clears the current match and moves the offset to the new index. */
+        void clear(int index) {
+            offset = index;
+            this.index = 0;
+            for (int i = 0; i < start.length; i++) {
+                start[i] = end[i] = -1;
+            }
+            next = null; // cut off alternates
+        }
+    
+        /**
+         * Returns the string matching the pattern.  This makes it convenient
+         * to write code like the following:
+         * <P>
+         * <code> 
+         * REMatch myMatch = myExpression.getMatch(myString);<br>
+         * if (myMatch != null) System.out.println("Regexp found: "+myMatch);
+         * </code>
+         */
+        public String toString() {
+            return matchedText;
+        }
+    
+        /**
+         * Returns the index within the input text where the match in its entirety
+         * began.
+         */
+        public int getStartIndex() {
+            return offset + start[0];
+        }
+    
+        /**
+         * Returns the index within the input string where the match in
+         * its entirety ends.  The return value is the next position after
+         * the end of the string; therefore, a match created by the
+         * following call:
+         *
+         * <P>
+         * <code>REMatch myMatch = myExpression.getMatch(myString);</code>
+         * <P>
+         * can be viewed (given that myMatch is not null) by creating
+         * <P>
+         * <code>String theMatch = myString.substring(myMatch.getStartIndex(),
+         * myMatch.getEndIndex());</code>
+         * <P>
+         * But you can save yourself that work, since the <code>toString()</code>
+         * method (above) does exactly that for you.  
+         */
+        public int getEndIndex() {
+            return offset + end[0];
+        }
+  
+        /**
+         * Returns the string matching the given subexpression.  The subexpressions
+         * are indexed starting with one, not zero.  That is, the subexpression
+         * identified by the first set of parentheses in a regular expression
+         * could be retrieved from an REMatch by calling match.toString(1).
+         *
+         * @param sub Index of the subexpression.
+         */
+        public String toString(int sub) {
+            if ((sub >= start.length) || (start[sub] == -1)) return "";
+            return (matchedText.substring(start[sub],end[sub]));
+        }
+    
+        /** 
+         * Returns the index within the input string used to generate this match
+         * where subexpression number <i>sub</i> begins, or <code>-1</code> if
+         * the subexpression does not exist.  The initial position is zero.
+         *
+         * @param sub Subexpression index
+         * @deprecated Use getStartIndex(int) instead.
+         */
+        public int getSubStartIndex(int sub) {
+            if (sub >= start.length) return -1;
+            int x = start[sub];
+            return (x == -1) ? x : offset + x;
+        }
+    
+        /** 
+         * Returns the index within the input string used to generate this match
+         * where subexpression number <i>sub</i> begins, or <code>-1</code> if
+         * the subexpression does not exist.  The initial position is zero.
+         *
+         * @param sub Subexpression index
+         * @since gnu.regexp 1.1.0
+         */
+        public int getStartIndex(int sub) {
+            if (sub >= start.length) return -1;
+            int x = start[sub];
+            return (x == -1) ? x : offset + x;
+        }
+  
+        /** 
+         * Returns the index within the input string used to generate this match
+         * where subexpression number <i>sub</i> ends, or <code>-1</code> if
+         * the subexpression does not exist.  The initial position is zero.
+         *
+         * @param sub Subexpression index
+         * @deprecated Use getEndIndex(int) instead
+         */
+        public int getSubEndIndex(int sub) {
+            if (sub >= start.length) return -1;
+            int x = end[sub];
+            return (x == -1) ? x : offset + x;
+        }
+    
+        /** 
+         * Returns the index within the input string used to generate this match
+         * where subexpression number <i>sub</i> ends, or <code>-1</code> if
+         * the subexpression does not exist.  The initial position is zero.
+         *
+         * @param sub Subexpression index
+         */
+        public int getEndIndex(int sub) {
+            if (sub >= start.length) return -1;
+            int x = end[sub];
+            return (x == -1) ? x : offset + x;
+        }
+    
+        /**
+         * Substitute the results of this match to create a new string.
+         * This is patterned after PERL, so the tokens to watch out for are
+         * <code>$0</code> through <code>$9</code>.  <code>$0</code> matches
+         * the full substring matched; <code>$<i>n</i></code> matches
+         * subexpression number <i>n</i>.
+         *
+         * @param input A string consisting of literals and <code>$<i>n</i></code> tokens.
+         */
+        public String substituteInto(String input) {
+            // a la Perl, $0 is whole thing, $1 - $9 are subexpressions
+            StringBuffer output = new StringBuffer();
+            int pos;
+            for (pos = 0; pos < input.length()-1; pos++) {
+                if ((input.charAt(pos) == '$') && (Character.isDigit(input.charAt(pos+1)))) {
+                    int val = Character.digit(input.charAt(++pos),10);
+                    if (val < start.length) {
+                        output.append(toString(val));
+                    } 
+                } else output.append(input.charAt(pos));
+            }
+            if (pos < input.length()) output.append(input.charAt(pos));
+            return output.toString();
+        }
+    }
+    /*
+     *  gnu/regexp/REMatchEnumeration.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    /**
+     * An REMatchEnumeration enumerates regular expression matches over a
+     * given input text.  You obtain a reference to an enumeration using
+     * the <code>getMatchEnumeration()</code> methods on an instance of
+     * RE. 
+     *
+     * <P>
+     *
+     * REMatchEnumeration does lazy computation; that is, it will not
+     * search for a match until it needs to.  If you'd rather just get all
+     * the matches at once in a big array, use the
+     * <code>getAllMatches()</code> methods on RE.  However, using an
+     * enumeration can help speed performance when the entire text does
+     * not need to be searched immediately.
+     *
+     * <P>
+     * 
+     * The enumerated type is especially useful when searching on a Reader
+     * or InputStream, because the InputStream read position cannot be
+     * guaranteed after calling <code>getMatch()</code> (see the
+     * description of that method for an explanation of why).  Enumeration
+     * also saves a lot of overhead required when calling
+     * <code>getMatch()</code> multiple times.
+     * 
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> 
+     */
+    public static class REMatchEnumeration implements Enumeration, Serializable {
+        private static final int YES = 1;
+        private static final int MAYBE = 0;
+        private static final int NO = -1;
+  
+        private int more;
+        private REMatch match;
+        private RE expr;
+        private CharIndexed input;
+        private int eflags;
+        private int index;
+
+        // Package scope constructor is used by RE.getMatchEnumeration()
+        REMatchEnumeration(RE expr, CharIndexed input, int index, int eflags) {
+            more = MAYBE;
+            this.expr = expr;
+            this.input = input;
+            this.index = index;
+            this.eflags = eflags;
+        }
+
+        /** Returns true if there are more matches in the input text. */
+        public boolean hasMoreElements() {
+            return hasMoreMatches(null);
+        }
+
+        /** Returns true if there are more matches in the input text. */
+        public boolean hasMoreMatches() {
+            return hasMoreMatches(null);
+        }
+
+        /** Returns true if there are more matches in the input text.
+         * Saves the text leading up to the match (or to the end of the input)
+         * in the specified buffer.
+         */
+        public boolean hasMoreMatches(StringBuffer buffer) {
+            if (more == MAYBE) {
+                match = expr.getMatchImpl(input,index,eflags,buffer);
+                if (match != null) {
+                    input.move((match.end[0] > 0) ? match.end[0] : 1);
+           
+                    index = (match.end[0] > 0) ? match.end[0] + match.offset : index + 1;
+                    more = YES;
+                } else more = NO;
+            }
+            return (more == YES);
+        }
+
+        /** Returns the next match in the input text. */
+        public Object nextElement() throws NoSuchElementException {
+            return nextMatch();
+        }
+
+        /** 
+         * Returns the next match in the input text. This method is provided
+         * for convenience to avoid having to explicitly cast the return value
+         * to class REMatch.
+         */
+        public REMatch nextMatch() throws NoSuchElementException {
+            if (hasMoreElements()) {
+                more = (input.isValid()) ? MAYBE : NO;
+                return match;
+            }
+            throw new NoSuchElementException();
+        }
+    }
+
+    /*
+     *  gnu/regexp/RESyntax.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    /**
+     * An RESyntax specifies the way a regular expression will be compiled.
+     * This class provides a number of predefined useful constants for
+     * emulating popular regular expression syntaxes.  Additionally the
+     * user may construct his or her own syntax, using any combination of the
+     * syntax bit constants.  The syntax is an optional argument to any of the
+     * matching methods on class RE.
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     */
+
+    public static final class RESyntax implements Serializable {
+        static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator");
+
+        private static final String SYNTAX_IS_FINAL = RE.getLocalizedMessage("syntax.final");
+
+        private BitSet bits;
+
+        // true for the constant defined syntaxes
+        private boolean isFinal = false;
+
+        private String lineSeparator = DEFAULT_LINE_SEPARATOR;
+
+        // Values for constants are bit indexes
+
+        /**
+         * Syntax bit. Backslash is an escape character in lists.
+         */
+        public static final int RE_BACKSLASH_ESCAPE_IN_LISTS =  0;
+
+        /**
+         * Syntax bit. Use \? instead of ? and \+ instead of +.
+         */
+        public static final int RE_BK_PLUS_QM                =  1;
+
+        /**
+         * Syntax bit. POSIX character classes ([:...:]) in lists are allowed.
+         */
+        public static final int RE_CHAR_CLASSES              =  2;
+
+        /**
+         * Syntax bit. ^ and $ are special everywhere.
+         * <B>Not implemented.</B>
+         */
+        public static final int RE_CONTEXT_INDEP_ANCHORS     =  3; 
+
+        /**
+         * Syntax bit. Repetition operators are only special in valid positions.
+         * <B>Not implemented.</B>
+         */
+        public static final int RE_CONTEXT_INDEP_OPS         =  4; 
+
+        /**
+         * Syntax bit. Repetition and alternation operators are invalid
+         * at start and end of pattern and other places. 
+         * <B>Not implemented</B>.
+         */
+        public static final int RE_CONTEXT_INVALID_OPS       =  5; 
+
+        /**
+         * Syntax bit. Match-any-character operator (.) matches a newline.
+         */
+        public static final int RE_DOT_NEWLINE               =  6;
+
+        /**
+         * Syntax bit. Match-any-character operator (.) does not match a null.
+         */
+        public static final int RE_DOT_NOT_NULL              =  7;
+
+        /**
+         * Syntax bit. Intervals ({x}, {x,}, {x,y}) are allowed.
+         */
+        public static final int RE_INTERVALS                 =  8;
+
+        /**
+         * Syntax bit. No alternation (|), match one-or-more (+), or 
+         * match zero-or-one (?) operators.
+         */
+        public static final int RE_LIMITED_OPS               =  9;
+
+        /**
+         * Syntax bit. Newline is an alternation operator.
+         */
+        public static final int RE_NEWLINE_ALT               = 10; // impl.
+
+        /**
+         * Syntax bit. Intervals use { } instead of \{ \}
+         */
+        public static final int RE_NO_BK_BRACES              = 11; 
+
+        /**
+         * Syntax bit. Grouping uses ( ) instead of \( \).
+         */
+        public static final int RE_NO_BK_PARENS              = 12;
+
+        /**
+         * Syntax bit. Backreferences not allowed.
+         */
+        public static final int RE_NO_BK_REFS                = 13;
+
+        /**
+         * Syntax bit. Alternation uses | instead of \|
+         */
+        public static final int RE_NO_BK_VBAR                = 14;
+
+        /**
+         * Syntax bit. <B>Not implemented</B>.
+         */
+        public static final int RE_NO_EMPTY_RANGES           = 15;
+
+        /**
+         * Syntax bit. An unmatched right parenthesis (')' or '\)', depending
+         * on RE_NO_BK_PARENS) will throw an exception when compiling.
+         */
+        public static final int RE_UNMATCHED_RIGHT_PAREN_ORD = 16;
+
+        /**
+         * Syntax bit. <B>Not implemented.</B>
+         */
+        public static final int RE_HAT_LISTS_NOT_NEWLINE     = 17;
+
+        /**
+         * Syntax bit.  Stingy matching is allowed (+?, *?, ??, {x,y}?).
+         */
+        public static final int RE_STINGY_OPS                = 18;
+
+        /**
+         * Syntax bit. Allow character class escapes (\d, \D, \s, \S, \w, \W).
+         */
+        public static final int RE_CHAR_CLASS_ESCAPES        = 19;
+
+        /**
+         * Syntax bit. Allow use of (?:xxx) grouping (subexpression is not saved).
+         */
+        public static final int RE_PURE_GROUPING             = 20;
+
+        /**
+         * Syntax bit. Allow use of (?=xxx) and (?!xxx) apply the subexpression
+         * to the text following the current position without consuming that text.
+         */
+        public static final int RE_LOOKAHEAD                 = 21;
+
+        /**
+         * Syntax bit. Allow beginning- and end-of-string anchors (\A, \Z).
+         */
+        public static final int RE_STRING_ANCHORS            = 22;
+
+        /**
+         * Syntax bit. Allow embedded comments, (?#comment), as in Perl5.
+         */
+        public static final int RE_COMMENTS                  = 23;
+
+        /**
+         * Syntax bit. Allow character class escapes within lists, as in Perl5.
+         */
+        public static final int RE_CHAR_CLASS_ESC_IN_LISTS   = 24;
+
+        private static final int BIT_TOTAL                   = 25;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the awk utility.
+         */
+        public static final RESyntax RE_SYNTAX_AWK;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the ed utility.
+         */
+        public static final RESyntax RE_SYNTAX_ED;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the egrep utility.
+         */
+        public static final RESyntax RE_SYNTAX_EGREP;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the GNU Emacs editor.
+         */
+        public static final RESyntax RE_SYNTAX_EMACS;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the grep utility.
+         */
+        public static final RESyntax RE_SYNTAX_GREP;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the POSIX awk specification.
+         */
+        public static final RESyntax RE_SYNTAX_POSIX_AWK;
+
+        /**
+         * Predefined syntax.
+         * Emulates POSIX basic regular expression support.
+         */
+        public static final RESyntax RE_SYNTAX_POSIX_BASIC;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the POSIX egrep specification.
+         */
+        public static final RESyntax RE_SYNTAX_POSIX_EGREP;
+
+        /**
+         * Predefined syntax.
+         * Emulates POSIX extended regular expression support.
+         */
+        public static final RESyntax RE_SYNTAX_POSIX_EXTENDED;
+
+        /**
+         * Predefined syntax.
+         * Emulates POSIX basic minimal regular expressions.
+         */
+        public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_BASIC;
+
+        /**
+         * Predefined syntax.
+         * Emulates POSIX extended minimal regular expressions.
+         */
+        public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_EXTENDED;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in the sed utility.
+         */
+        public static final RESyntax RE_SYNTAX_SED;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in Larry Wall's perl, version 4,
+         */
+        public static final RESyntax RE_SYNTAX_PERL4;
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in Larry Wall's perl, version 4,
+         * using single line mode (/s modifier).
+         */
+        public static final RESyntax RE_SYNTAX_PERL4_S; // single line mode (/s)
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in Larry Wall's perl, version 5.
+         */
+        public static final RESyntax RE_SYNTAX_PERL5;  
+
+        /**
+         * Predefined syntax.
+         * Emulates regular expression support in Larry Wall's perl, version 5,
+         * using single line mode (/s modifier).
+         */
+        public static final RESyntax RE_SYNTAX_PERL5_S;
+  
+        static {
+            // Define syntaxes
+      
+            RE_SYNTAX_EMACS = new RESyntax().makeFinal();
+      
+            RESyntax RE_SYNTAX_POSIX_COMMON = new RESyntax()
+                .set(RE_CHAR_CLASSES)
+                .set(RE_DOT_NEWLINE)
+                .set(RE_DOT_NOT_NULL)
+                .set(RE_INTERVALS)
+                .set(RE_NO_EMPTY_RANGES)
+                .makeFinal();
+      
+            RE_SYNTAX_POSIX_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+                .set(RE_BK_PLUS_QM)
+                .makeFinal();
+      
+            RE_SYNTAX_POSIX_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+                .set(RE_CONTEXT_INDEP_ANCHORS)
+                .set(RE_CONTEXT_INDEP_OPS)
+                .set(RE_NO_BK_BRACES)
+                .set(RE_NO_BK_PARENS)
+                .set(RE_NO_BK_VBAR)
+                .set(RE_UNMATCHED_RIGHT_PAREN_ORD)
+                .makeFinal();
+
+            RE_SYNTAX_AWK = new RESyntax()
+                .set(RE_BACKSLASH_ESCAPE_IN_LISTS)
+                .set(RE_DOT_NOT_NULL)
+                .set(RE_NO_BK_PARENS)
+                .set(RE_NO_BK_REFS)
+                .set(RE_NO_BK_VBAR)
+                .set(RE_NO_EMPTY_RANGES)
+                .set(RE_UNMATCHED_RIGHT_PAREN_ORD)
+                .makeFinal();
+      
+            RE_SYNTAX_POSIX_AWK = new RESyntax(RE_SYNTAX_POSIX_EXTENDED)
+                .set(RE_BACKSLASH_ESCAPE_IN_LISTS)
+                .makeFinal();
+      
+            RE_SYNTAX_GREP = new RESyntax()
+                .set(RE_BK_PLUS_QM)
+                .set(RE_CHAR_CLASSES)
+                .set(RE_HAT_LISTS_NOT_NEWLINE)
+                .set(RE_INTERVALS)
+                .set(RE_NEWLINE_ALT)
+                .makeFinal();
+      
+            RE_SYNTAX_EGREP = new RESyntax()
+                .set(RE_CHAR_CLASSES)
+                .set(RE_CONTEXT_INDEP_ANCHORS)
+                .set(RE_CONTEXT_INDEP_OPS)
+                .set(RE_HAT_LISTS_NOT_NEWLINE)
+                .set(RE_NEWLINE_ALT)
+                .set(RE_NO_BK_PARENS)
+                .set(RE_NO_BK_VBAR)
+                .makeFinal();
+    
+            RE_SYNTAX_POSIX_EGREP = new RESyntax(RE_SYNTAX_EGREP)
+                .set(RE_INTERVALS)
+                .set(RE_NO_BK_BRACES)
+                .makeFinal();
+    
+            /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff.  */
+    
+            RE_SYNTAX_ED = new RESyntax(RE_SYNTAX_POSIX_BASIC)
+                .makeFinal();
+    
+            RE_SYNTAX_SED = new RESyntax(RE_SYNTAX_POSIX_BASIC)
+                .makeFinal();
+      
+            RE_SYNTAX_POSIX_MINIMAL_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+                .set(RE_LIMITED_OPS)
+                .makeFinal();
+      
+            /* Differs from RE_SYNTAX_POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
+               replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */
+      
+            RE_SYNTAX_POSIX_MINIMAL_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+                .set(RE_CONTEXT_INDEP_ANCHORS)
+                .set(RE_CONTEXT_INVALID_OPS)
+                .set(RE_NO_BK_BRACES)
+                .set(RE_NO_BK_PARENS)
+                .set(RE_NO_BK_REFS)
+                .set(RE_NO_BK_VBAR)
+                .set(RE_UNMATCHED_RIGHT_PAREN_ORD)
+                .makeFinal();
+      
+            /* There is no official Perl spec, but here's a "best guess" */
+      
+            RE_SYNTAX_PERL4 = new RESyntax()
+                .set(RE_BACKSLASH_ESCAPE_IN_LISTS)
+                .set(RE_CONTEXT_INDEP_ANCHORS)
+                .set(RE_CONTEXT_INDEP_OPS)          // except for '{', apparently
+                .set(RE_INTERVALS)
+                .set(RE_NO_BK_BRACES)
+                .set(RE_NO_BK_PARENS)
+                .set(RE_NO_BK_VBAR)
+                .set(RE_NO_EMPTY_RANGES)
+                .set(RE_CHAR_CLASS_ESCAPES)    // \d,\D,\w,\W,\s,\S
+                .makeFinal();
+      
+            RE_SYNTAX_PERL4_S = new RESyntax(RE_SYNTAX_PERL4)
+                .set(RE_DOT_NEWLINE)
+                .makeFinal();
+      
+            RE_SYNTAX_PERL5 = new RESyntax(RE_SYNTAX_PERL4)
+                .set(RE_PURE_GROUPING)          // (?:)
+                .set(RE_STINGY_OPS)             // *?,??,+?,{}?
+                .set(RE_LOOKAHEAD)              // (?=)(?!)
+                .set(RE_STRING_ANCHORS)         // \A,\Z
+                .set(RE_CHAR_CLASS_ESC_IN_LISTS)// \d,\D,\w,\W,\s,\S within []
+                .set(RE_COMMENTS)              // (?#)
+                .makeFinal();
+      
+            RE_SYNTAX_PERL5_S = new RESyntax(RE_SYNTAX_PERL5)
+                .set(RE_DOT_NEWLINE)
+                .makeFinal();
+        }
+
+        /**
+         * Construct a new syntax object with all bits turned off.
+         * This is equivalent to RE_SYNTAX_EMACS.
+         */
+        public RESyntax() {
+            bits = new BitSet(BIT_TOTAL);
+        }
+
+        /**
+         * Called internally when constructing predefined syntaxes
+         * so their interpretation cannot vary.  Conceivably useful
+         * for your syntaxes as well.  Causes IllegalAccessError to
+         * be thrown if any attempt to modify the syntax is made.
+         *
+         * @return this object for convenient chaining
+         */
+        public RESyntax makeFinal() {
+            isFinal = true;
+            return this;
+        }
+
+        /**
+         * Construct a new syntax object with all bits set the same 
+         * as the other syntax.
+         */
+        public RESyntax(RESyntax other) {
+            bits = (BitSet) other.bits.clone();
+        }
+
+        /**
+         * Check if a given bit is set in this syntax.
+         */
+        public boolean get(int index) {
+            return bits.get(index);
+        }
+
+        /**
+         * Set a given bit in this syntax. 
+         *
+         * @param index the constant (RESyntax.RE_xxx) bit to set.
+         * @return a reference to this object for easy chaining.
+         */
+        public RESyntax set(int index) {
+            if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL);
+            bits.set(index);
+            return this;
+        }
+
+        /**
+         * Clear a given bit in this syntax. 
+         *
+         * @param index the constant (RESyntax.RE_xxx) bit to clear.
+         * @return a reference to this object for easy chaining.
+         */
+        public RESyntax clear(int index) {
+            if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL);
+            bits.clear(index);
+            return this;
+        }
+
+        /**
+         * Changes the line separator string for regular expressions
+         * created using this RESyntax.  The default separator is the
+         * value returned by the system property "line.separator", which
+         * should be correct when reading platform-specific files from a
+         * filesystem.  However, many programs may collect input from
+         * sources where the line separator is differently specified (for
+         * example, in the applet environment, the text box widget
+         * interprets line breaks as single-character newlines,
+         * regardless of the host platform.
+         *
+         * Note that setting the line separator to a character or
+         * characters that have specific meaning within the current syntax
+         * can cause unexpected chronosynclastic infundibula.
+         *
+         * @return this object for convenient chaining 
+         */
+        public RESyntax setLineSeparator(String aSeparator) {
+            if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL);
+            lineSeparator = aSeparator;
+            return this;
+        }
+
+        /**
+         * Returns the currently active line separator string.  The default
+         * is the platform-dependent system property "line.separator".
+         */
+        public String getLineSeparator() {
+            return lineSeparator;
+        }
+    }
+    /*
+     *  gnu/regexp/REToken.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    abstract static class REToken implements Serializable {
+
+        protected REToken next = null;
+        protected REToken uncle = null;
+        protected int subIndex;
+
+        protected REToken(int subIndex) {
+            this.subIndex = subIndex;
+        }
+
+        int getMinimumLength() {
+            return 0;
+        }
+
+        void setUncle(REToken anUncle) {
+            uncle = anUncle;
+        }
+
+        /** Returns true if the match succeeded, false if it failed. */
+        abstract boolean match(CharIndexed input, REMatch mymatch);
+  
+        /** Returns true if the rest of the tokens match, false if they fail. */
+        protected boolean next(CharIndexed input, REMatch mymatch) {
+            if (next == null) {
+                if (uncle == null) {
+                    return true;
+                } else {
+                    return uncle.match(input, mymatch);
+                }
+            } else {
+                return next.match(input, mymatch);
+            }
+        }
+  
+        boolean chain(REToken token) {
+            next = token;
+            return true; // Token was accepted
+        }
+
+        abstract void dump(StringBuffer os);
+
+        void dumpAll(StringBuffer os) {
+            dump(os);
+            if (next != null) next.dumpAll(os);
+        }
+    }
+    /*
+     *  gnu/regexp/RETokenAny.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenAny extends REToken {
+        /** True if '.' can match a newline (RE_DOT_NEWLINE) */
+        private boolean newline; 
+
+        /** True if '.' can't match a null (RE_DOT_NOT_NULL) */
+        private boolean matchNull;    
+  
+        RETokenAny(int subIndex, boolean newline, boolean matchNull) { 
+            super(subIndex);
+            this.newline = newline;
+            this.matchNull = matchNull;
+        }
+
+        int getMinimumLength() {
+            return 1;
+        }
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            char ch = input.charAt(mymatch.index);
+            if ((ch == CharIndexed.OUT_OF_BOUNDS)
+                || (!newline && (ch == '\n'))
+                || (matchNull && (ch == 0))) {
+                return false;
+            }
+            ++mymatch.index;
+            return next(input, mymatch);
+        }
+
+        void dump(StringBuffer os) {
+            os.append('.');
+        }
+    }
+
+    /*
+     *  gnu/regexp/RETokenBackRef.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenBackRef extends REToken {
+        private int num;
+        private boolean insens;
+  
+        RETokenBackRef(int subIndex, int num, boolean insens) {
+            super(subIndex);
+            this.num = num;
+            this.insens = insens;
+        }
+
+        // should implement getMinimumLength() -- any ideas?
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            int b,e;
+            b = mymatch.start[num];
+            e = mymatch.end[num];
+            if ((b==-1)||(e==-1)) return false; // this shouldn't happen, but...
+            for (int i=b; i<e; i++) {
+                if (input.charAt(mymatch.index+i-b) != input.charAt(i)) {
+                    return false;
+                }
+            }
+            mymatch.index += e-b;
+            return next(input, mymatch);
+        }
+    
+        void dump(StringBuffer os) {
+            os.append('\\').append(num);
+        }
+    }
+
+
+    /*
+     *  gnu/regexp/RETokenChar.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenChar extends REToken {
+        private char[] ch;
+        private boolean insens;
+
+        RETokenChar(int subIndex, char c, boolean ins) {
+            super(subIndex);
+            ch = new char [1];
+            ch[0] = (insens = ins) ? Character.toLowerCase(c) : c;
+        }
+
+        int getMinimumLength() {
+            return ch.length;
+        }
+  
+        boolean match(CharIndexed input, REMatch mymatch) {
+            int z = ch.length;
+            char c;
+            for (int i=0; i<z; i++) {
+                c = input.charAt(mymatch.index+i);
+                if (( (insens) ? Character.toLowerCase(c) : c ) != ch[i]) {
+                    return false;
+                }
+            }
+            mymatch.index += z;
+
+            return next(input, mymatch);
+        }
+
+        // Overrides REToken.chain() to optimize for strings
+        boolean chain(REToken next) {
+            if (next instanceof RETokenChar) {
+                RETokenChar cnext = (RETokenChar) next;
+                // assume for now that next can only be one character
+                int newsize = ch.length + cnext.ch.length;
+      
+                char[] chTemp = new char [newsize];
+      
+                System.arraycopy(ch,0,chTemp,0,ch.length);
+                System.arraycopy(cnext.ch,0,chTemp,ch.length,cnext.ch.length);
+      
+                ch = chTemp;
+                return false;
+            } else return super.chain(next);
+        }
+
+        void dump(StringBuffer os) {
+            os.append(ch);
+        }
+    }
+
+
+    /*
+     *  gnu/regexp/RETokenEnd.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    final static class RETokenEnd extends REToken {
+        /**
+         * Indicates whether this token should match on a line break.
+         */
+        private String newline;
+
+        RETokenEnd(int subIndex,String newline) { 
+            super(subIndex);
+            this.newline = newline;
+        }
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            char ch = input.charAt(mymatch.index);
+            if (ch == CharIndexed.OUT_OF_BOUNDS)
+                return ((mymatch.eflags & RE.REG_NOTEOL)>0) ? 
+                    false : next(input, mymatch);
+            if (newline != null) {
+                char z;
+                int i = 0; // position in newline
+                do {
+                    z = newline.charAt(i);
+                    if (ch != z) return false;
+                    ++i;
+                    ch = input.charAt(mymatch.index + i);
+                } while (i < newline.length());
+           
+                return next(input, mymatch);
+            }
+            return false;
+        }
+
+        void dump(StringBuffer os) {
+            os.append('$');
+        }
+    }
+    /*
+     *  gnu/regexp/RETokenEndSub.java
+     *  Copyright (C) 2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenEndSub extends REToken {
+        RETokenEndSub(int subIndex) {
+            super(subIndex);
+        }
+    
+        boolean match(CharIndexed input, REMatch mymatch) {
+            mymatch.end[subIndex] = mymatch.index;
+            return next(input, mymatch);
+        }
+    
+        void dump(StringBuffer os) {
+            // handled by RE
+        }
+    }
+    /*
+     *  gnu/regexp/RETokenOneOf.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+    /**
+     * @since gnu.regexp 1.1.3
+     * @author Shashank Bapat
+     */
+    final static class RETokenLookAhead extends REToken
+    {
+        REToken re;
+        boolean negative;
+
+        RETokenLookAhead(REToken re, boolean negative) throws REException {
+            super(0);
+            this.re = re;
+            this.negative = negative;
+        }
+
+        boolean match(CharIndexed input, REMatch mymatch)
+        {
+            REMatch trymatch = (REMatch)mymatch.clone();
+            REMatch trymatch1 = (REMatch)mymatch.clone();
+            REMatch newMatch = null;
+            if (re.match(input, trymatch)) {
+                if (negative) return false;
+                if (next(input, trymatch1))
+                    newMatch = trymatch1;
+            }
+
+            if (newMatch != null) {
+                if (negative) return false;
+                //else
+                mymatch.assignFrom(newMatch);
+                return true;
+            }
+            else { // no match
+                if (negative)
+                    return next(input, mymatch);
+                //else
+                return false;
+            }
+        }
+
+        void dump(StringBuffer os) {
+            os.append("(?");
+            os.append(negative ? '!' : '=');
+            re.dumpAll(os);
+            os.append(')');
+        }
+    }
+
+    /*
+     *  gnu/regexp/RETokenOneOf.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenOneOf extends REToken {
+        private Vector options;
+        private boolean negative;
+
+        // This constructor is used for convenience when we know the set beforehand,
+        // e.g. \d --> new RETokenOneOf("0123456789",false, ..)
+        //      \D --> new RETokenOneOf("0123456789",true, ..)
+
+        RETokenOneOf(int subIndex, String optionsStr, boolean negative, boolean insens) {
+            super(subIndex);
+            options = new Vector();
+            this.negative = negative;
+            for (int i = 0; i < optionsStr.length(); i++)
+                options.addElement(new RETokenChar(subIndex,optionsStr.charAt(i),insens));
+        }
+
+        RETokenOneOf(int subIndex, Vector options, boolean negative) {
+            super(subIndex);
+            this.options = options;
+            this.negative = negative;
+        }
+
+        int getMinimumLength() {
+            int min = Integer.MAX_VALUE;
+            int x;
+            for (int i=0; i < options.size(); i++) {
+                if ((x = ((REToken) options.elementAt(i)).getMinimumLength()) < min)
+                    min = x;
+            }
+            return min;
+        }
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            if (negative && (input.charAt(mymatch.index) == CharIndexed.OUT_OF_BOUNDS)) 
+                return false;
+
+            REMatch newMatch = null;
+            REMatch last = null;
+            REToken tk;
+            boolean isMatch;
+            for (int i=0; i < options.size(); i++) {
+                tk = (REToken) options.elementAt(i);
+                REMatch tryMatch = (REMatch) mymatch.clone();
+                if (tk.match(input, tryMatch)) { // match was successful
+                    if (negative) return false;
+
+                    if (next(input, tryMatch)) {
+                        // Add tryMatch to list of possibilities.
+                        if (last == null) {
+                            newMatch = tryMatch;
+                            last = tryMatch;
+                        } else {
+                            last.next = tryMatch;
+                            last = tryMatch;
+                        }
+                    } // next succeeds
+                } // is a match
+            } // try next option
+
+            if (newMatch != null) {
+                if (negative) {
+                    return false;
+                } else {
+                    // set contents of mymatch equal to newMatch
+
+                    // try each one that matched
+                    mymatch.assignFrom(newMatch);
+                    return true;
+                }
+            } else {
+                if (negative) {
+                    ++mymatch.index;
+                    return next(input, mymatch);
+                } else {
+                    return false;
+                }
+            }
+
+            // index+1 works for [^abc] lists, not for generic lookahead (--> index)
+        }
+
+        void dump(StringBuffer os) {
+            os.append(negative ? "[^" : "(?:");
+            for (int i = 0; i < options.size(); i++) {
+                if (!negative && (i > 0)) os.append('|');
+                ((REToken) options.elementAt(i)).dumpAll(os);
+            }
+            os.append(negative ? ']' : ')');
+        }  
+    }
+    /*
+     *  gnu/regexp/RETokenPOSIX.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenPOSIX extends REToken {
+        int type;
+        boolean insens;
+        boolean negated;
+
+        static final int  ALNUM = 0;
+        static final int  ALPHA = 1;
+        static final int  BLANK = 2;
+        static final int  CNTRL = 3;
+        static final int  DIGIT = 4;
+        static final int  GRAPH = 5;
+        static final int  LOWER = 6;
+        static final int  PRINT = 7;
+        static final int  PUNCT = 8;
+        static final int  SPACE = 9;
+        static final int  UPPER = 10;
+        static final int XDIGIT = 11;
+
+        // Array indices correspond to constants defined above.
+        static final String[] s_nameTable =  {
+            "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower",
+            "print", "punct", "space", "upper", "xdigit" 
+        };
+
+        // The RE constructor uses this to look up the constant for a string
+        static int intValue(String key) {
+            for (int i = 0; i < s_nameTable.length; i++) {
+                if (s_nameTable[i].equals(key)) return i;
+            }
+            return -1;
+        }
+
+        RETokenPOSIX(int subIndex, int type, boolean insens, boolean negated) {
+            super(subIndex);
+            this.type = type;
+            this.insens = insens;
+            this.negated = negated;
+        }
+
+        int getMinimumLength() {
+            return 1;
+        }
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            char ch = input.charAt(mymatch.index);
+            if (ch == CharIndexed.OUT_OF_BOUNDS)
+                return false;
+    
+            boolean retval = false;
+            switch (type) {
+                case ALNUM:
+                    // Note that there is some debate over whether '_' should be included
+                    retval = Character.isLetterOrDigit(ch) || (ch == '_');
+                    break;
+                case ALPHA:
+                    retval = Character.isLetter(ch);
+                    break;
+                case BLANK:
+                    retval = ((ch == ' ') || (ch == '\t'));
+                    break;
+                case CNTRL:
+                    retval = Character.isISOControl(ch);
+                    break;
+                case DIGIT:
+                    retval = Character.isDigit(ch);
+                    break;
+                case GRAPH:
+                    retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch)));
+                    break;
+                case LOWER:
+                    retval = ((insens && Character.isLetter(ch)) || Character.isLowerCase(ch));
+                    break;
+                case PRINT:
+                    retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch)))
+                        || (ch == ' ');
+                    break;
+                case PUNCT:
+                    // This feels sloppy, especially for non-U.S. locales.
+                    retval = ("`~!@#$%^&*()-_=+[]{}\\|;:'\"/?,.<>".indexOf(ch)!=-1);
+                    break;
+                case SPACE:
+                    retval = Character.isWhitespace(ch);
+                    break;
+                case UPPER:
+                    retval = ((insens && Character.isLetter(ch)) || Character.isUpperCase(ch));
+                    break;
+                case XDIGIT:
+                    retval = (Character.isDigit(ch) || ("abcdefABCDEF".indexOf(ch)!=-1));
+                    break;
+            }
+
+            if (negated) retval = !retval;
+            if (retval) {
+                ++mymatch.index;
+                return next(input, mymatch);
+            }
+            else return false;
+        }
+
+        void dump(StringBuffer os) {
+            if (negated) os.append('^');
+            os.append("[:" + s_nameTable[type] + ":]");
+        }
+    }
+    /*
+     *  gnu/regexp/RETokenRange.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenRange extends REToken {
+        private char lo, hi;
+        private boolean insens;
+
+        RETokenRange(int subIndex, char lo, char hi, boolean ins) {
+            super(subIndex);
+            this.lo = (insens = ins) ? Character.toLowerCase(lo) : lo;
+            this.hi = ins ? Character.toLowerCase(hi) : hi;
+        }
+
+        int getMinimumLength() {
+            return 1;
+        }
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            char c = input.charAt(mymatch.index);
+            if (c == CharIndexed.OUT_OF_BOUNDS) return false;
+            if (insens) c = Character.toLowerCase(c);
+            if ((c >= lo) && (c <= hi)) {
+                ++mymatch.index;
+                return next(input, mymatch);
+            }
+            return false;
+        }
+    
+        void dump(StringBuffer os) {
+            os.append(lo).append('-').append(hi);
+        }
+    }
+
+    /*
+     *  gnu/regexp/RETokenRepeated.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    final static class RETokenRepeated extends REToken {
+        private REToken token;
+        private int min,max;
+        private boolean stingy;
+    
+        RETokenRepeated(int subIndex, REToken token, int min, int max) {
+            super(subIndex);
+            this.token = token;
+            this.min = min;
+            this.max = max;
+        }
+
+        /** Sets the minimal matching mode to true. */
+        void makeStingy() {
+            stingy = true;
+        }
+    
+        /** Queries if this token has minimal matching enabled. */
+        boolean isStingy() {
+            return stingy;
+        }
+    
+        /**
+         * The minimum length of a repeated token is the minimum length
+         * of the token multiplied by the minimum number of times it must
+         * match.
+         */
+        int getMinimumLength() {
+            return (min * token.getMinimumLength());
+        }
+
+        // We do need to save every possible point, but the number of clone()
+        // invocations here is really a killer for performance on non-stingy
+        // repeat operators.  I'm open to suggestions...
+
+        // Hypothetical question: can you have a RE that matches 1 times,
+        // 3 times, 5 times, but not 2 times or 4 times?  Does having
+        // the subexpression back-reference operator allow that?
+
+        boolean match(CharIndexed input, REMatch mymatch) {
+            // number of times we've matched so far
+            int numRepeats = 0; 
+       
+            // Possible positions for the next repeat to match at
+            REMatch newMatch = mymatch;
+            REMatch last = null;
+            REMatch current;
+
+            // Add the '0-repeats' index
+            // positions.elementAt(z) == position [] in input after <<z>> matches
+            Vector positions = new Vector();
+            positions.addElement(newMatch);
+       
+            // Declare variables used in loop
+            REMatch doables;
+            REMatch doablesLast;
+            REMatch recurrent;
+
+            do {
+                // Check for stingy match for each possibility.
+                if (stingy && (numRepeats >= min)) {
+                    REMatch result = matchRest(input, newMatch);
+                    if (result != null) {
+                        mymatch.assignFrom(result);
+                        return true;
+                    }
+                }
+
+                doables = null;
+                doablesLast = null;
+
+                // try next repeat at all possible positions
+                for (current = newMatch; current != null; current = current.next) {
+                    recurrent = (REMatch) current.clone();
+                    if (token.match(input, recurrent)) {
+                        // add all items in current to doables array
+                        if (doables == null) {
+                            doables = recurrent;
+                            doablesLast = recurrent;
+                        } else {
+                            // Order these from longest to shortest
+                            // Start by assuming longest (more repeats)
+                            doablesLast.next = recurrent;
+                        }
+                        // Find new doablesLast
+                        while (doablesLast.next != null) {
+                            doablesLast = doablesLast.next;
+                        }
+                    }
+                }
+                // if none of the possibilities worked out, break out of do/while
+                if (doables == null) break;
+           
+                // reassign where the next repeat can match
+                newMatch = doables;
+           
+                // increment how many repeats we've successfully found
+                ++numRepeats;
+           
+                positions.addElement(newMatch);
+            } while (numRepeats < max);
+       
+            // If there aren't enough repeats, then fail
+            if (numRepeats < min) return false;
+       
+            // We're greedy, but ease off until a true match is found 
+            int posIndex = positions.size();
+       
+            // At this point we've either got too many or just the right amount.
+            // See if this numRepeats works with the rest of the regexp.
+            REMatch allResults = null;
+            REMatch allResultsLast = null;
+
+            REMatch results = null;
+            while (--posIndex >= min) {
+                newMatch = (REMatch) positions.elementAt(posIndex);
+                results = matchRest(input, newMatch);
+                if (results != null) {
+                    if (allResults == null) {
+                        allResults = results;
+                        allResultsLast = results;
+                    } else {
+                        // Order these from longest to shortest
+                        // Start by assuming longest (more repeats)
+                        allResultsLast.next = results;
+                    }
+                    // Find new doablesLast
+                    while (allResultsLast.next != null) {
+                        allResultsLast = allResultsLast.next;
+                    }
+                }
+                // else did not match rest of the tokens, try again on smaller sample
+            }
+            if (allResults != null) {
+                mymatch.assignFrom(allResults); // does this get all?
+                return true;
+            }
+            // If we fall out, no matches.
+            return false;
+        }
+
+        private REMatch matchRest(CharIndexed input, final REMatch newMatch) {
+            REMatch current, single;
+            REMatch doneIndex = null;
+            REMatch doneIndexLast = null;
+            // Test all possible matches for this number of repeats
+            for (current = newMatch; current != null; current = current.next) {
+                // clone() separates a single match from the chain
+                single = (REMatch) current.clone();
+                if (next(input, single)) {
+                    // chain results to doneIndex
+                    if (doneIndex == null) {
+                        doneIndex = single;
+                        doneIndexLast = single;
+                    } else {
+                        doneIndexLast.next = single;
+                    }
+                    // Find new doneIndexLast
+                    while (doneIndexLast.next != null) {
+                        doneIndexLast = doneIndexLast.next;
+                    }
+                }
+            }
+            return doneIndex;
+        }
+
+        void dump(StringBuffer os) {
+            os.append("(?:");
+            token.dumpAll(os);
+            os.append(')');
+            if ((max == Integer.MAX_VALUE) && (min <= 1))
+                os.append( (min == 0) ? '*' : '+' );
+            else if ((min == 0) && (max == 1))
+                os.append('?');
+            else {
+                os.append('{').append(min);
+                if (max > min) {
+                    os.append(',');
+                    if (max != Integer.MAX_VALUE) os.append(max);
+                }
+                os.append('}');
+            }
+            if (stingy) os.append('?');
+        }
+    }
+    /*
+     *  gnu/regexp/RETokenStart.java
+     *  Copyright (C) 1998-2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    static class RETokenStart extends REToken {
+        private String newline; // matches after a newline
+    
+        RETokenStart(int subIndex, String newline) {
+            super(subIndex);
+            this.newline = newline;
+        }
+    
+        boolean match(CharIndexed input, REMatch mymatch) {
+            // charAt(index-n) may be unknown on a Reader/InputStream. FIXME
+            // Match after a newline if in multiline mode
+       
+            if (newline != null) {
+                int len = newline.length();
+                if (mymatch.offset >= len) {
+                    boolean found = true;
+                    char z;
+                    int i = 0; // position in REToken.newline
+                    char ch = input.charAt(mymatch.index - len);
+                    do {
+                        z = newline.charAt(i);
+                        if (ch != z) {
+                            found = false;
+                            break;
+                        }
+                        ++i;
+                        ch = input.charAt(mymatch.index - len + i);
+                    } while (i < len);
+           
+                    if (found) return next(input, mymatch);
+                }
+            }
+       
+            // Don't match at all if REG_NOTBOL is set.
+            if ((mymatch.eflags & RE.REG_NOTBOL) > 0) return false;
+       
+            if ((mymatch.eflags & RE.REG_ANCHORINDEX) > 0)
+                return (mymatch.anchor == mymatch.offset) ? 
+                    next(input, mymatch) : false;
+            else
+                return ((mymatch.index == 0) && (mymatch.offset == 0)) ?
+                    next(input, mymatch) : false;
+        }
+    
+        void dump(StringBuffer os) {
+            os.append('^');
+        }
+    }
+    /*
+     *  gnu/regexp/RETokenWordBoundary.java
+     *  Copyright (C) 2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    /**
+     * Represents a combination lookahead/lookbehind for POSIX [:alnum:].
+     */
+    final static class RETokenWordBoundary extends REToken {
+        private boolean negated;
+        private int where;
+        static final int BEGIN = 1;
+        static final int END = 2;
+
+        RETokenWordBoundary(int subIndex, int where, boolean negated) {
+            super(subIndex);
+            this.where = where;
+            this.negated = negated;
+        }
+    
+        boolean match(CharIndexed input, REMatch mymatch) {
+            // Word boundary means input[index-1] was a word character
+            // and input[index] is not, or input[index] is a word character
+            // and input[index-1] was not
+            //  In the string "one two three", these positions match:
+            //  |o|n|e| |t|w|o| |t|h|r|e|e|
+            //  ^     ^ ^     ^ ^         ^
+            boolean after = false;  // is current character a letter or digit?
+            boolean before = false; // is previous character a letter or digit?
+            char ch;
+
+            // TODO: Also check REG_ANCHORINDEX vs. anchor
+            if (((mymatch.eflags & RE.REG_ANCHORINDEX) != RE.REG_ANCHORINDEX) 
+                || (mymatch.offset + mymatch.index > mymatch.anchor)) {
+                if ((ch = input.charAt(mymatch.index - 1)) != CharIndexed.OUT_OF_BOUNDS) {
+                    before = Character.isLetterOrDigit(ch) || (ch == '_');
+                }
+            }
+
+            if ((ch = input.charAt(mymatch.index)) != CharIndexed.OUT_OF_BOUNDS) {
+                after = Character.isLetterOrDigit(ch) || (ch == '_');
+            }
+
+            // if (before) and (!after), we're at end (\>)
+            // if (after) and (!before), we're at beginning (\<)
+            boolean doNext = false;
+
+            if ((where & BEGIN) == BEGIN) {
+                doNext = after && !before;
+            }
+            if ((where & END) == END) {
+                doNext ^= before && !after;
+            }
+
+            if (negated) doNext = !doNext;
+
+            return (doNext ? next(input, mymatch) : false);
+        }
+    
+        void dump(StringBuffer os) {
+            if (where == (BEGIN | END)) {
+                os.append( negated ? "\\B" : "\\b" );
+            } else if (where == BEGIN) {
+                os.append("\\<");
+            } else {
+                os.append("\\>");
+            }
+        }
+    }
+    /*
+     *  gnu/regexp/UncheckedRE.java
+     *  Copyright (C) 2001 Wes Biggs
+     *
+     *  This library is free software; you can redistribute it and/or modify
+     *  it under the terms of the GNU Lesser General Public License as published
+     *  by the Free Software Foundation; either version 2.1 of the License, or
+     *  (at your option) any later version.
+     *
+     *  This library is distributed in the hope that it will be useful,
+     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *  GNU Lesser General Public License for more details.
+     *
+     *  You should have received a copy of the GNU Lesser General Public License
+     *  along with this program; if not, write to the Free Software
+     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+     */
+
+
+    /**
+     * UncheckedRE is a subclass of RE that allows programmers an easier means
+     * of programmatically precompiling regular expressions.  It is constructed
+     * and used in exactly the same manner as an instance of the RE class; the
+     * only difference is that its constructors do not throw REException.
+     * Instead, if a syntax error is encountered during construction, a
+     * RuntimeException will be thrown.
+     * <P>
+     * Note that this makes UncheckedRE dangerous if constructed with
+     * dynamic data.  Do not use UncheckedRE unless you are completely sure
+     * that all input being passed to it contains valid, well-formed 
+     * regular expressions for the syntax specified.
+     *
+     * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
+     * @see gnu.regexp.RE 
+     * @since gnu.regexp 1.1.4
+     */
+
+    public final static class UncheckedRE extends RE {
+        /**
+         * Constructs a regular expression pattern buffer without any compilation
+         * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5).
+         *
+         * @param pattern A regular expression pattern, in the form of a String,
+         *   StringBuffer or char[].  Other input types will be converted to
+         *   strings using the toString() method.
+         * @exception RuntimeException The input pattern could not be parsed.
+         * @exception NullPointerException The pattern was null.
+         */
+        public UncheckedRE(Object pattern) {
+            this(pattern,0,RESyntax.RE_SYNTAX_PERL5);
+        }
+
+        /**
+         * Constructs a regular expression pattern buffer using the specified
+         * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5).
+         *
+         * @param pattern A regular expression pattern, in the form of a String,
+         *   StringBuffer, or char[].  Other input types will be converted to
+         *   strings using the toString() method.
+         * @param cflags The logical OR of any combination of the compilation flags in the RE class.
+         * @exception RuntimeException The input pattern could not be parsed.
+         * @exception NullPointerException The pattern was null.
+         */
+        public UncheckedRE(Object pattern, int cflags) {
+            this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5);
+        }
+
+        /**
+         * Constructs a regular expression pattern buffer using the specified
+         * compilation flags and regular expression syntax.
+         *
+         * @param pattern A regular expression pattern, in the form of a String,
+         *   StringBuffer, or char[].  Other input types will be converted to
+         *   strings using the toString() method.
+         * @param cflags The logical OR of any combination of the compilation flags in the RE class.
+         * @param syntax The type of regular expression syntax to use.
+         * @exception RuntimeException The input pattern could not be parsed.
+         * @exception NullPointerException The pattern was null.
+         */
+        public UncheckedRE(Object pattern, int cflags, RESyntax syntax) {
+            try {
+                initialize(pattern,cflags,syntax,0,0);
+            } catch (REException e) { 
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+    }
+}
+
index 69fb9b3..364792d 100644 (file)
@@ -40,7 +40,6 @@ public class JSReflection extends JS {
         } catch (NoSuchFieldException nfe) {
         } catch (IllegalAccessException nfe) {
         } catch (SecurityException nfe) { }
-
         try {
             Method[] methods = this.getClass().getMethods();
             for(int i=0; i<methods.length; i++) if (methods[i].getName().equals(k)) return METHOD;
index da22d2e..fb9e67d 100644 (file)
@@ -1,12 +1,10 @@
 // Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
 package org.ibex.js;
 
-import gnu.regexp.*;
-
 /** A JavaScript regular expression object */
 public class JSRegexp extends JS {
     private boolean global;
-    private RE re;
+    private GnuRegexp.RE re;
     private int lastIndex;
     
     public JSRegexp(Object arg0, Object arg1) throws JSExn {
@@ -23,8 +21,8 @@ public class JSRegexp extends JS {
             if(sFlags == null) sFlags = "";
             for(int i=0;i<sFlags.length();i++) {
                 switch(sFlags.charAt(i)) {
-                    case 'i': flags |= RE.REG_ICASE; break;
-                    case 'm': flags |= RE.REG_MULTILINE; break;
+                    case 'i': flags |= GnuRegexp.RE.REG_ICASE; break;
+                    case 'm': flags |= GnuRegexp.RE.REG_MULTILINE; break;
                     case 'g': global = true; break;
                     default: throw new JSExn("Invalid flag in regexp \"" + sFlags.charAt(i) + "\"");
                 }
@@ -32,8 +30,8 @@ public class JSRegexp extends JS {
             re = newRE(pattern,flags);
             put("source", pattern);
             put("global", B(global));
-            put("ignoreCase", B(flags & RE.REG_ICASE));
-            put("multiline", B(flags & RE.REG_MULTILINE));
+            put("ignoreCase", B(flags & GnuRegexp.RE.REG_ICASE));
+            put("multiline", B(flags & GnuRegexp.RE.REG_MULTILINE));
         }
     }
 
@@ -45,7 +43,7 @@ public class JSRegexp extends JS {
                     String s = (String)a0;
                     int start = global ? lastIndex : 0;
                     if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
-                    REMatch match = re.getMatch(s,start);
+                    GnuRegexp.REMatch match = re.getMatch(s,start);
                     if(global) lastIndex = match == null ? s.length() : match.getEndIndex();
                     return match == null ? null : matchToExecResult(match,re,s);
                 }
@@ -54,7 +52,7 @@ public class JSRegexp extends JS {
                     if (!global) return B(re.getMatch(s) != null);
                     int start = global ? lastIndex : 0;
                     if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
-                    REMatch match = re.getMatch(s,start);
+                    GnuRegexp.REMatch match = re.getMatch(s,start);
                     lastIndex = match != null ? s.length() : match.getEndIndex();
                     return B(match != null);
                 }
@@ -89,7 +87,7 @@ public class JSRegexp extends JS {
         super.put(key,value);
     }
   
-    private static Object matchToExecResult(REMatch match, RE re, String s) {
+    private static Object matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
         try {
             JS ret = new JS();
             ret.put("index", N(match.getStartIndex()));
@@ -121,7 +119,7 @@ public class JSRegexp extends JS {
     
     public static Object stringMatch(Object o, Object arg0) throws JSExn {
         String s = o.toString();
-        RE re;
+        GnuRegexp.RE re;
         JSRegexp regexp = null;
         if(arg0 instanceof JSRegexp) {
             regexp = (JSRegexp) arg0;
@@ -131,13 +129,13 @@ public class JSRegexp extends JS {
         }
         
         if(regexp == null) {
-            REMatch match = re.getMatch(s);
+            GnuRegexp.REMatch match = re.getMatch(s);
             return matchToExecResult(match,re,s);
         }
         if(!regexp.global) return regexp.callMethod("exec", s, null, null, null, 1);
         
         JSArray ret = new JSArray();
-        REMatch[] matches = re.getAllMatches(s);
+        GnuRegexp.REMatch[] matches = re.getAllMatches(s);
         for(int i=0;i<matches.length;i++) ret.addElement(matches[i].toString());
         regexp.lastIndex = matches.length > 0 ? matches[matches.length-1].getEndIndex() : s.length();
         return ret;
@@ -145,14 +143,14 @@ public class JSRegexp extends JS {
     
     public static Object stringSearch(Object o, Object arg0) throws JSExn  {
         String s = o.toString();
-        RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0);
-        REMatch match = re.getMatch(s);
+        GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0);
+        GnuRegexp.REMatch match = re.getMatch(s);
         return match == null ? N(-1) : N(match.getStartIndex());
     }
     
     public static Object stringReplace(Object o, Object arg0, Object arg1) throws JSExn {
         String s = o.toString();
-        RE re;
+        GnuRegexp.RE re;
         JSFunction replaceFunc = null;
         String replaceString = null;
         JSRegexp regexp = null;
@@ -166,7 +164,7 @@ public class JSRegexp extends JS {
             replaceFunc = (JSFunction) arg1;
         else
             replaceString = JS.toString(arg1.toString());
-        REMatch[] matches;
+        GnuRegexp.REMatch[] matches;
         if(regexp != null && regexp.global) {
             matches = re.getAllMatches(s);
             if(regexp != null) {
@@ -176,18 +174,18 @@ public class JSRegexp extends JS {
                     regexp.lastIndex = s.length();
             }
         } else {
-            REMatch match = re.getMatch(s);
+            GnuRegexp.REMatch match = re.getMatch(s);
             if(match != null)
-                matches = new REMatch[]{ match };
+                matches = new GnuRegexp.REMatch[]{ match };
             else
-                matches = new REMatch[0];
+                matches = new GnuRegexp.REMatch[0];
         }
         
         StringBuffer sb = new StringBuffer(s.length());
         int pos = 0;
         char[] sa = s.toCharArray();
         for(int i=0;i<matches.length;i++) {
-            REMatch match = matches[i];
+            GnuRegexp.REMatch match = matches[i];
             sb.append(sa,pos,match.getStartIndex()-pos);
             pos = match.getEndIndex();
             if(replaceFunc != null) {
@@ -229,7 +227,7 @@ public class JSRegexp extends JS {
         return sb.toString();
     }
     
-    private static String mySubstitute(REMatch match, String s, String source) {
+    private static String mySubstitute(GnuRegexp.REMatch match, String s, String source) {
         StringBuffer sb = new StringBuffer();
         int i,n;
         char c,c2;
@@ -276,7 +274,7 @@ public class JSRegexp extends JS {
         if(limit < 0) limit = Integer.MAX_VALUE;
         if(limit == 0) return new JSArray();
         
-        RE re = null;
+        GnuRegexp.RE re = null;
         JSRegexp regexp = null;
         String sep = null;
         JSArray ret = new JSArray();
@@ -300,7 +298,7 @@ public class JSRegexp extends JS {
         
         OUTER: while(p < s.length()) {
             if(re != null) {
-                REMatch m = re.getMatch(s,p);
+                GnuRegexp.REMatch m = re.getMatch(s,p);
                 if(m == null) break OUTER;
                 boolean zeroLength = m.getStartIndex() == m.getEndIndex();
                 ret.addElement(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex()));
@@ -324,10 +322,10 @@ public class JSRegexp extends JS {
         return ret;
     }
     
-    public static RE newRE(String pattern, int flags) throws JSExn {
+    public static GnuRegexp.RE newRE(String pattern, int flags) throws JSExn {
         try {
-            return new RE(pattern,flags,RESyntax.RE_SYNTAX_PERL5);
-        } catch(REException e) {
+            return new GnuRegexp.RE(pattern,flags,GnuRegexp.RESyntax.RE_SYNTAX_PERL5);
+        } catch(GnuRegexp.REException e) {
             throw new JSExn(e.toString());
         }
     }