| /* 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 ('?'); |
| } |
| } |