mirror of git://gcc.gnu.org/git/gcc.git
640 lines
18 KiB
Java
640 lines
18 KiB
Java
/* gnu/regexp/RETokenRepeated.java
|
|
Copyright (C) 2006 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GNU Classpath 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
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA.
|
|
|
|
Linking this library statically or dynamically with other modules is
|
|
making a combined work based on this library. Thus, the terms and
|
|
conditions of the GNU General Public License cover the whole
|
|
combination.
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
permission to link this library with independent modules to produce an
|
|
executable, regardless of the license terms of these independent
|
|
modules, and to copy and distribute the resulting executable under
|
|
terms of your choice, provided that you also meet, for each linked
|
|
independent module, the terms and conditions of the license of that
|
|
module. An independent module is a module which is not derived from
|
|
or based on this library. If you modify this library, you may extend
|
|
this exception to your version of the library, but you are not
|
|
obligated to do so. If you do not wish to do so, delete this
|
|
exception statement from your version. */
|
|
|
|
|
|
package gnu.java.util.regex;
|
|
|
|
import gnu.java.lang.CPStringBuilder;
|
|
|
|
import java.util.ArrayDeque;
|
|
import java.util.Deque;
|
|
|
|
final class RETokenRepeated extends REToken
|
|
{
|
|
private REToken token;
|
|
private int min, max;
|
|
private boolean stingy;
|
|
private boolean possessive;
|
|
private int tokenFixedLength;
|
|
|
|
RETokenRepeated (int subIndex, REToken token, int min, int max)
|
|
{
|
|
super (subIndex);
|
|
this.token = token;
|
|
this.min = min;
|
|
this.max = max;
|
|
if (token.returnsFixedLengthMatches ())
|
|
{
|
|
tokenFixedLength = token.getMaximumLength ();
|
|
}
|
|
else
|
|
{
|
|
tokenFixedLength = -1;
|
|
}
|
|
}
|
|
|
|
/** Sets the minimal matching mode to true. */
|
|
void makeStingy ()
|
|
{
|
|
stingy = true;
|
|
}
|
|
|
|
/** Queries if this token has minimal matching enabled. */
|
|
boolean isStingy ()
|
|
{
|
|
return stingy;
|
|
}
|
|
|
|
/** Sets possessive matching mode to true. */
|
|
void makePossessive ()
|
|
{
|
|
possessive = true;
|
|
}
|
|
|
|
/** Queries if this token has possessive matching enabled. */
|
|
boolean isPossessive ()
|
|
{
|
|
return possessive;
|
|
}
|
|
|
|
/**
|
|
* 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 ());
|
|
}
|
|
|
|
int getMaximumLength ()
|
|
{
|
|
if (max == Integer.MAX_VALUE)
|
|
return Integer.MAX_VALUE;
|
|
int tmax = token.getMaximumLength ();
|
|
if (tmax == Integer.MAX_VALUE)
|
|
return tmax;
|
|
return (max * tmax);
|
|
}
|
|
|
|
// The comment "MUST make a clone" below means that some tests
|
|
// failed without doing clone(),
|
|
|
|
private static class DoablesFinder
|
|
{
|
|
private REToken tk;
|
|
private CharIndexed input;
|
|
private REMatch rematch;
|
|
private boolean findFirst;
|
|
|
|
private DoablesFinder (REToken tk, CharIndexed input, REMatch mymatch)
|
|
{
|
|
this.tk = tk;
|
|
this.input = input;
|
|
this.rematch = (REMatch) mymatch.clone (); // MUST make a clone
|
|
this.rematch.backtrackStack = new BacktrackStack ();
|
|
findFirst = true;
|
|
}
|
|
|
|
private REMatch find ()
|
|
{
|
|
int origin = rematch.index;
|
|
REMatch rem;
|
|
if (findFirst)
|
|
{
|
|
rem = tk.findMatch (input, rematch);
|
|
findFirst = false;
|
|
}
|
|
else
|
|
{
|
|
while (true)
|
|
{
|
|
if (rematch.backtrackStack.empty ())
|
|
{
|
|
rem = null;
|
|
break;
|
|
}
|
|
BacktrackStack.Backtrack bt = rematch.backtrackStack.pop ();
|
|
rem = bt.token.backtrack (bt.input, bt.match, bt.param);
|
|
if (rem != null)
|
|
break;
|
|
}
|
|
}
|
|
if (rem == null)
|
|
return null;
|
|
if (rem.index == origin)
|
|
rem.empty = true;
|
|
rematch = rem;
|
|
return (REMatch) rem.clone (); // MUST make a clone.
|
|
}
|
|
|
|
boolean noMore ()
|
|
{
|
|
return rematch.backtrackStack.empty ();
|
|
}
|
|
}
|
|
|
|
REMatch findMatch (CharIndexed input, REMatch mymatch)
|
|
{
|
|
if (tokenFixedLength >= 0)
|
|
return findMatchFixedLength (input, mymatch);
|
|
BacktrackStack stack = new BacktrackStack ();
|
|
stack.push (new StackedInfo (input, 0, mymatch, null, null));
|
|
return findMatch (stack);
|
|
}
|
|
|
|
REMatch backtrack (CharIndexed input, REMatch mymatch, Object param)
|
|
{
|
|
if (tokenFixedLength >= 0)
|
|
return backtrackFixedLength (input, mymatch, param);
|
|
return findMatch ((BacktrackStack) param);
|
|
}
|
|
|
|
private static class StackedInfo extends BacktrackStack.Backtrack
|
|
{
|
|
int numRepeats;
|
|
int[] visited;
|
|
DoablesFinder finder;
|
|
StackedInfo (CharIndexed input, int numRepeats, REMatch match,
|
|
int[]visited, DoablesFinder finder)
|
|
{
|
|
super (null, input, match, null);
|
|
this.numRepeats = numRepeats;
|
|
this.visited = visited;
|
|
this.finder = finder;
|
|
}
|
|
}
|
|
|
|
private static class FindMatchControl
|
|
{
|
|
DoablesFinder finder;
|
|
FindMatchControl (DoablesFinder finder)
|
|
{
|
|
this.finder = finder;
|
|
}
|
|
}
|
|
|
|
private REMatch findMatch (BacktrackStack stack)
|
|
{
|
|
return findMatch (stack, new ArrayDeque < FindMatchControl > ());
|
|
}
|
|
|
|
private REMatch findMatch (BacktrackStack stack,
|
|
Deque < FindMatchControl > controlStack)
|
|
{
|
|
REMatch result = null;
|
|
StackedInfo si = null;
|
|
CharIndexed input = null;
|
|
int numRepeats = 0;
|
|
REMatch mymatch = null;
|
|
int[] visited = null;
|
|
DoablesFinder finder = null;
|
|
|
|
// Avoid using recursive calls because a match can be very long.
|
|
|
|
// This is the first entry point of this method.
|
|
// If you want to call this method recursively and you need the
|
|
// result returned, save necessary information in a FindMatchControl
|
|
// object and push it to controlStack, then continue from this point.
|
|
// You can check the result after exiting MAIN_LOOP.
|
|
MAIN_LOOP0:
|
|
while (true)
|
|
{
|
|
|
|
// This is the second entry point of this method.
|
|
// If you want to call this method recursively but you do not need the
|
|
// result returned, just continue from this point.
|
|
MAIN_LOOP:
|
|
while (true)
|
|
{
|
|
|
|
if (stack.empty ())
|
|
break MAIN_LOOP;
|
|
si = (StackedInfo) (stack.peek ());
|
|
input = si.input;
|
|
numRepeats = si.numRepeats;
|
|
mymatch = si.match;
|
|
visited = si.visited;
|
|
finder = si.finder;
|
|
|
|
if (mymatch.backtrackStack == null)
|
|
mymatch.backtrackStack = new BacktrackStack ();
|
|
|
|
if (numRepeats >= max)
|
|
{
|
|
stack.pop ();
|
|
REMatch m1 = matchRest (input, mymatch);
|
|
if (m1 != null)
|
|
{
|
|
if (!stack.empty ())
|
|
{
|
|
m1.backtrackStack.push (new BacktrackStack.
|
|
Backtrack (this, input,
|
|
mymatch, stack));
|
|
}
|
|
result = m1;
|
|
break MAIN_LOOP;
|
|
}
|
|
if (stingy)
|
|
{
|
|
continue MAIN_LOOP;
|
|
}
|
|
break MAIN_LOOP;
|
|
}
|
|
|
|
if (finder == null)
|
|
{
|
|
finder = new DoablesFinder (token, input, mymatch);
|
|
si.finder = finder;
|
|
}
|
|
|
|
if (numRepeats < min)
|
|
{
|
|
while (true)
|
|
{
|
|
REMatch doable = finder.find ();
|
|
if (doable == null)
|
|
{
|
|
if (stack.empty ())
|
|
return null;
|
|
stack.pop ();
|
|
continue MAIN_LOOP;
|
|
}
|
|
if (finder.noMore ())
|
|
stack.pop ();
|
|
int newNumRepeats = (doable.empty ? min : numRepeats + 1);
|
|
stack.
|
|
push (new
|
|
StackedInfo (input, newNumRepeats, doable,
|
|
visited, null));
|
|
continue MAIN_LOOP;
|
|
}
|
|
}
|
|
|
|
if (visited == null)
|
|
visited = initVisited ();
|
|
|
|
if (stingy)
|
|
{
|
|
REMatch nextMatch = finder.find ();
|
|
if (nextMatch != null && !nextMatch.empty)
|
|
{
|
|
stack.
|
|
push (new
|
|
StackedInfo (input, numRepeats + 1, nextMatch,
|
|
visited, null));
|
|
}
|
|
else
|
|
{
|
|
stack.pop ();
|
|
}
|
|
REMatch m1 = matchRest (input, mymatch);
|
|
if (m1 != null)
|
|
{
|
|
if (!stack.empty ())
|
|
{
|
|
m1.backtrackStack.push (new BacktrackStack.
|
|
Backtrack (this, input,
|
|
mymatch, stack));
|
|
}
|
|
result = m1;
|
|
break MAIN_LOOP;
|
|
}
|
|
else
|
|
{
|
|
continue MAIN_LOOP;
|
|
}
|
|
}
|
|
|
|
visited = addVisited (mymatch.index, visited);
|
|
|
|
TryAnotherResult taresult =
|
|
tryAnother (stack, input, mymatch, numRepeats, finder, visited);
|
|
visited = taresult.visited;
|
|
switch (taresult.status)
|
|
{
|
|
case TryAnotherResult.TRY_FURTHER:
|
|
controlStack.push (new FindMatchControl (finder));
|
|
continue MAIN_LOOP0;
|
|
case TryAnotherResult.RESULT_FOUND:
|
|
result = taresult.result;
|
|
break MAIN_LOOP;
|
|
}
|
|
|
|
if (!stack.empty ())
|
|
{
|
|
stack.pop ();
|
|
}
|
|
if (possessive)
|
|
{
|
|
stack.clear ();
|
|
}
|
|
REMatch m1 = matchRest (input, mymatch);
|
|
if (m1 != null)
|
|
{
|
|
if (!stack.empty ())
|
|
{
|
|
m1.backtrackStack.push (new BacktrackStack.
|
|
Backtrack (this, input, mymatch,
|
|
stack));
|
|
}
|
|
result = m1;
|
|
break MAIN_LOOP;
|
|
}
|
|
|
|
} // MAIN_LOOP
|
|
|
|
if (controlStack.isEmpty ())
|
|
return result;
|
|
FindMatchControl control = controlStack.pop ();
|
|
if (possessive)
|
|
{
|
|
return result;
|
|
}
|
|
if (result != null)
|
|
{
|
|
result.backtrackStack.push (new BacktrackStack.
|
|
Backtrack (this, input, mymatch,
|
|
stack));
|
|
return result;
|
|
}
|
|
|
|
finder = control.finder;
|
|
|
|
TryAnotherResult taresult =
|
|
tryAnother (stack, input, mymatch, numRepeats, finder, visited);
|
|
visited = taresult.visited;
|
|
switch (taresult.status)
|
|
{
|
|
case TryAnotherResult.TRY_FURTHER:
|
|
controlStack.push (new FindMatchControl (finder));
|
|
continue MAIN_LOOP0;
|
|
case TryAnotherResult.RESULT_FOUND:
|
|
return taresult.result;
|
|
}
|
|
continue MAIN_LOOP0;
|
|
|
|
} // MAIN_LOOP0
|
|
}
|
|
|
|
private static class TryAnotherResult
|
|
{
|
|
REMatch result;
|
|
int status;
|
|
static final int RESULT_FOUND = 1;
|
|
static final int TRY_FURTHER = 2;
|
|
static final int NOTHING_FOUND = 3;
|
|
int[] visited;
|
|
}
|
|
|
|
private TryAnotherResult tryAnother (BacktrackStack stack,
|
|
CharIndexed input, REMatch mymatch,
|
|
int numRepeats, DoablesFinder finder,
|
|
int[]visited)
|
|
{
|
|
|
|
TryAnotherResult taresult = new TryAnotherResult ();
|
|
taresult.visited = visited;
|
|
|
|
DO_THIS:
|
|
{
|
|
|
|
boolean emptyMatchFound = false;
|
|
|
|
DO_ONE_DOABLE:
|
|
while (true)
|
|
{
|
|
|
|
REMatch doable = finder.find ();
|
|
if (doable == null)
|
|
{
|
|
break DO_THIS;
|
|
}
|
|
if (doable.empty)
|
|
emptyMatchFound = true;
|
|
|
|
if (!emptyMatchFound)
|
|
{
|
|
int n = doable.index;
|
|
if (visitedContains (n, visited))
|
|
{
|
|
continue DO_ONE_DOABLE;
|
|
}
|
|
visited = addVisited (n, visited);
|
|
stack.
|
|
push (new
|
|
StackedInfo (input, numRepeats + 1, doable, visited,
|
|
null));
|
|
taresult.visited = visited;
|
|
taresult.status = TryAnotherResult.TRY_FURTHER;
|
|
return taresult;
|
|
}
|
|
else
|
|
{
|
|
REMatch m1 = matchRest (input, doable);
|
|
if (possessive)
|
|
{
|
|
taresult.result = m1;
|
|
taresult.status = TryAnotherResult.RESULT_FOUND;
|
|
return taresult;
|
|
}
|
|
if (m1 != null)
|
|
{
|
|
if (!stack.empty ())
|
|
{
|
|
m1.backtrackStack.push (new BacktrackStack.
|
|
Backtrack (this, input, mymatch,
|
|
stack));
|
|
}
|
|
taresult.result = m1;
|
|
taresult.status = TryAnotherResult.RESULT_FOUND;
|
|
return taresult;
|
|
}
|
|
}
|
|
|
|
} // DO_ONE_DOABLE
|
|
|
|
} // DO_THIS
|
|
|
|
taresult.status = TryAnotherResult.NOTHING_FOUND;
|
|
return taresult;
|
|
|
|
}
|
|
|
|
boolean match (CharIndexed input, REMatch mymatch)
|
|
{
|
|
setHitEnd (input, mymatch);
|
|
REMatch m1 = findMatch (input, mymatch);
|
|
if (m1 != null)
|
|
{
|
|
mymatch.assignFrom (m1);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Array visited is an array of character positions we have already
|
|
// visited. visited[0] is used to store the effective length of the
|
|
// array.
|
|
private static int[] initVisited ()
|
|
{
|
|
int[] visited = new int[32];
|
|
visited[0] = 0;
|
|
return visited;
|
|
}
|
|
|
|
private static boolean visitedContains (int n, int[]visited)
|
|
{
|
|
// Experience tells that for a small array like this,
|
|
// simple linear search is faster than binary search.
|
|
for (int i = 1; i < visited[0]; i++)
|
|
{
|
|
if (n == visited[i])
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static int[] addVisited (int n, int[]visited)
|
|
{
|
|
if (visitedContains (n, visited))
|
|
return visited;
|
|
if (visited[0] >= visited.length - 1)
|
|
{
|
|
int[] newvisited = new int[visited.length + 32];
|
|
System.arraycopy (visited, 0, newvisited, 0, visited.length);
|
|
visited = newvisited;
|
|
}
|
|
visited[0]++;
|
|
visited[visited[0]] = n;
|
|
return visited;
|
|
}
|
|
|
|
private REMatch matchRest (CharIndexed input, final REMatch newMatch)
|
|
{
|
|
if (next (input, newMatch))
|
|
{
|
|
return newMatch;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private REMatch findMatchFixedLength (CharIndexed input, REMatch mymatch)
|
|
{
|
|
if (mymatch.backtrackStack == null)
|
|
mymatch.backtrackStack = new BacktrackStack ();
|
|
int numRepeats =
|
|
token.findFixedLengthMatches (input, (REMatch) mymatch.clone (), max);
|
|
if (numRepeats == Integer.MAX_VALUE)
|
|
numRepeats = min;
|
|
int count = numRepeats - min + 1;
|
|
if (count <= 0)
|
|
return null;
|
|
int index = 0;
|
|
if (!stingy)
|
|
index = mymatch.index + (tokenFixedLength * numRepeats);
|
|
else
|
|
index = mymatch.index + (tokenFixedLength * min);
|
|
return findMatchFixedLength (input, mymatch, index, count);
|
|
}
|
|
|
|
private REMatch backtrackFixedLength (CharIndexed input, REMatch mymatch,
|
|
Object param)
|
|
{
|
|
int[] params = (int[]) param;
|
|
int index = params[0];
|
|
int count = params[1];
|
|
return findMatchFixedLength (input, mymatch, index, count);
|
|
}
|
|
|
|
private REMatch findMatchFixedLength (CharIndexed input, REMatch mymatch,
|
|
int index, int count)
|
|
{
|
|
REMatch tryMatch = (REMatch) mymatch.clone ();
|
|
while (true)
|
|
{
|
|
tryMatch.index = index;
|
|
REMatch m = matchRest (input, tryMatch);
|
|
count--;
|
|
if (stingy)
|
|
index += tokenFixedLength;
|
|
else
|
|
index -= tokenFixedLength;
|
|
if (possessive)
|
|
return m;
|
|
if (m != null)
|
|
{
|
|
if (count > 0)
|
|
{
|
|
m.backtrackStack.push (new BacktrackStack.
|
|
Backtrack (this, input, mymatch,
|
|
new int[]
|
|
{
|
|
index, count}));
|
|
}
|
|
return m;
|
|
}
|
|
if (count <= 0)
|
|
return null;
|
|
}
|
|
}
|
|
|
|
void dump (CPStringBuilder 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 ('?');
|
|
}
|
|
}
|