Removed a bunch of unused code (let's go!!!)
This commit is contained in:
		| @@ -2,7 +2,6 @@ package regex | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"sort" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // A Match represents a match found by the regex in a given string. | // A Match represents a match found by the regex in a given string. | ||||||
| @@ -69,30 +68,6 @@ func getZeroGroup(m Match) Group { | |||||||
| 	return m[0] | 	return m[0] | ||||||
| } | } | ||||||
|  |  | ||||||
| // Prunes the slice by removing overlapping indices. |  | ||||||
| func pruneIndices(indices []Match) []Match { |  | ||||||
| 	// First, sort the slice by the start indices |  | ||||||
| 	sort.Slice(indices, func(i, j int) bool { |  | ||||||
| 		return indices[i][0].StartIdx < indices[j][0].StartIdx |  | ||||||
| 	}) |  | ||||||
| 	toRet := make([]Match, 0, len(indices)) |  | ||||||
| 	current := indices[0] |  | ||||||
| 	for _, idx := range indices[1:] { |  | ||||||
| 		// idx doesn't overlap with current (starts after current ends), so add current to result |  | ||||||
| 		// and update the current. |  | ||||||
| 		if idx[0].StartIdx >= current[0].EndIdx { |  | ||||||
| 			toRet = append(toRet, current) |  | ||||||
| 			current = idx |  | ||||||
| 		} else if idx[0].EndIdx > current[0].EndIdx { |  | ||||||
| 			// idx overlaps, but it is longer, so update current |  | ||||||
| 			current = idx |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Add last state |  | ||||||
| 	toRet = append(toRet, current) |  | ||||||
| 	return toRet |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func copyThread(to *nfaState, from nfaState) { | func copyThread(to *nfaState, from nfaState) { | ||||||
| 	to.threadGroups = append([]Group{}, from.threadGroups...) | 	to.threadGroups = append([]Group{}, from.threadGroups...) | ||||||
| } | } | ||||||
| @@ -223,9 +198,6 @@ func (regex Reg) FindAllSubmatch(str string) []Match { | |||||||
| 			indices = append(indices, matchIdx) | 			indices = append(indices, matchIdx) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if len(indices) > 0 { |  | ||||||
| 		return pruneIndices(indices) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return indices | 	return indices | ||||||
| } | } | ||||||
| @@ -272,8 +244,6 @@ func addStateToList(str []rune, idx int, list []nfaState, state nfaState, thread | |||||||
| // Helper for FindAllMatches. Returns whether it found a match, the | // Helper for FindAllMatches. Returns whether it found a match, the | ||||||
| // first Match it finds, and how far it got into the string ie. where | // first Match it finds, and how far it got into the string ie. where | ||||||
| // the next search should start from. | // the next search should start from. | ||||||
| // |  | ||||||
| //	Might return duplicates or overlapping indices, so care must be taken to prune the resulting array. |  | ||||||
| func findAllSubmatchHelper(start *nfaState, str []rune, offset int, numGroups int) (bool, Match, int) { | func findAllSubmatchHelper(start *nfaState, str []rune, offset int, numGroups int) (bool, Match, int) { | ||||||
| 	// Base case - exit if offset exceeds string's length | 	// Base case - exit if offset exceeds string's length | ||||||
| 	if offset > len(str) { | 	if offset > len(str) { | ||||||
| @@ -282,21 +252,9 @@ func findAllSubmatchHelper(start *nfaState, str []rune, offset int, numGroups in | |||||||
| 	} | 	} | ||||||
| 	resetThreads(start) | 	resetThreads(start) | ||||||
|  |  | ||||||
| 	// Hold a list of match indices for the current run. When we |  | ||||||
| 	// can no longer find a match, the match with the largest range is |  | ||||||
| 	// chosen as the match for the entire string. |  | ||||||
| 	// This allows us to pick the longest possible match (which is how greedy matching works). |  | ||||||
| 	// COMMENT ABOVE IS CURRENTLY NOT UP-TO-DATE |  | ||||||
| 	//	tempIndices := newMatch(numGroups + 1) |  | ||||||
|  |  | ||||||
| 	//	foundPath := false |  | ||||||
| 	//startIdx := offset |  | ||||||
| 	//endIdx := offset |  | ||||||
| 	currentStates := make([]nfaState, 0) | 	currentStates := make([]nfaState, 0) | ||||||
| 	nextStates := make([]nfaState, 0) | 	nextStates := make([]nfaState, 0) | ||||||
| 	//	tempStates := make([]*nfaState, 0) // Used to store states that should be used in next loop iteration |  | ||||||
| 	i := offset // Index in string | 	i := offset // Index in string | ||||||
| 	//startingFrom := i                  // Store starting index |  | ||||||
|  |  | ||||||
| 	// If the first state is an assertion, makes sure the assertion | 	// If the first state is an assertion, makes sure the assertion | ||||||
| 	// is true before we do _anything_ else. | 	// is true before we do _anything_ else. | ||||||
| @@ -306,29 +264,11 @@ func findAllSubmatchHelper(start *nfaState, str []rune, offset int, numGroups in | |||||||
| 			return false, []Group{}, i | 			return false, []Group{}, i | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	// Increment until we hit a character matching the start state (assuming not 0-state) |  | ||||||
| 	//	if start.isEmpty == false { |  | ||||||
| 	//		for i < len(str) && !start.contentContains(str, i) { |  | ||||||
| 	//			i++ |  | ||||||
| 	//		} |  | ||||||
| 	//		startIdx = i |  | ||||||
| 	//		startingFrom = i |  | ||||||
| 	//		i++ // Advance to next character (if we aren't at a 0-state, which doesn't match anything), so that we can check for transitions. If we advance at a 0-state, we will never get a chance to match the first character |  | ||||||
| 	//	} |  | ||||||
|  |  | ||||||
| 	//	start.threadGroups = newMatch(numGroups + 1) |  | ||||||
| 	// Check if the start state begins a group - if so, add the start index to our list |  | ||||||
| 	//if start.groupBegin { |  | ||||||
| 	//		start.threadGroups[start.groupNum].StartIdx = i |  | ||||||
| 	//		tempIndices[start.groupNum].startIdx = i |  | ||||||
| 	//} |  | ||||||
|  |  | ||||||
| 	start.threadGroups = newMatch(numGroups + 1) | 	start.threadGroups = newMatch(numGroups + 1) | ||||||
| 	start.threadGroups[0].StartIdx = i | 	start.threadGroups[0].StartIdx = i | ||||||
| 	currentStates = addStateToList(str, i, currentStates, *start, start.threadGroups, nil) | 	currentStates = addStateToList(str, i, currentStates, *start, start.threadGroups, nil) | ||||||
| 	var match Match = nil | 	var match Match = nil | ||||||
| 	//	var isEmptyAndNoAssertion bool |  | ||||||
| 	// Main loop |  | ||||||
| 	for idx := i; idx <= len(str); idx++ { | 	for idx := i; idx <= len(str); idx++ { | ||||||
| 		if len(currentStates) == 0 { | 		if len(currentStates) == 0 { | ||||||
| 			break | 			break | ||||||
| @@ -350,76 +290,6 @@ func findAllSubmatchHelper(start *nfaState, str []rune, offset int, numGroups in | |||||||
| 					nextStates = addStateToList(str, idx+1, nextStates, *currentState.next, currentState.threadGroups, nil) | 					nextStates = addStateToList(str, idx+1, nextStates, *currentState.next, currentState.threadGroups, nil) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			//			if currentState.groupBegin { |  | ||||||
| 			//				currentState.threadGroups[currentState.groupNum].StartIdx = idx |  | ||||||
| 			//			} |  | ||||||
| 			//			if currentState.groupEnd { |  | ||||||
| 			//				currentState.threadGroups[currentState.groupNum].EndIdx = idx |  | ||||||
| 			//			} |  | ||||||
|  |  | ||||||
| 			// Alternation - enqueue left then right state, and continue |  | ||||||
| 			//			if currentState.isAlternation { |  | ||||||
| 			//				if currentState.isKleene { // Reverse order of adding things |  | ||||||
| 			//					rightState := currentState.splitState |  | ||||||
| 			//					copyThread(rightState, currentState) |  | ||||||
| 			//					currentStates = slices.Insert(currentStates, currentStateIdx+1, *rightState) |  | ||||||
| 			//					leftState := currentState.next |  | ||||||
| 			//					copyThread(leftState, currentState) |  | ||||||
| 			//					currentStates = slices.Insert(currentStates, currentStateIdx+2, *leftState) |  | ||||||
| 			//				} else { |  | ||||||
| 			//					leftState := currentState.next |  | ||||||
| 			//					copyThread(leftState, currentState) |  | ||||||
| 			//					currentStates = slices.Insert(currentStates, currentStateIdx+1, *leftState) |  | ||||||
| 			//					rightState := currentState.splitState |  | ||||||
| 			//					copyThread(rightState, currentState) |  | ||||||
| 			//					currentStates = slices.Insert(currentStates, currentStateIdx+2, *rightState) |  | ||||||
| 			//				} |  | ||||||
| 			//				continue |  | ||||||
| 			//			} |  | ||||||
|  |  | ||||||
| 			// Empty state - enqueue next state, do _not_ increment the SP |  | ||||||
| 			//			if !currentState.isAlternation && currentState.isEmpty && currentState.assert == noneAssert { //&& currentState.groupBegin == false && currentState.groupEnd == false { |  | ||||||
| 			//				isEmptyAndNoAssertion = true |  | ||||||
| 			//			} |  | ||||||
| 			// |  | ||||||
| 			//			if currentState.contentContains(str, idx) { |  | ||||||
| 			//				foundMatch = true |  | ||||||
| 			//			} |  | ||||||
| 			// |  | ||||||
| 			//			if isEmptyAndNoAssertion || foundMatch { |  | ||||||
| 			//				nextMatch := *(currentState.next) |  | ||||||
| 			//				copyThread(&nextMatch, currentState) |  | ||||||
| 			//				if currentState.groupBegin { |  | ||||||
| 			//					//	if !stateExists(currentStates, nextMatch) { |  | ||||||
| 			//					currentStates = slices.Insert(currentStates, currentStateIdx+1, nextMatch) |  | ||||||
| 			//					//} |  | ||||||
| 			//				} else if currentState.groupEnd { |  | ||||||
| 			//					if !stateExists(currentStates, nextMatch) { |  | ||||||
| 			//						currentStates = slices.Insert(currentStates, currentStateIdx+1, nextMatch) // append(currentStates, nextMatch) |  | ||||||
| 			//					} |  | ||||||
| 			//				} else if currentState.assert != noneAssert { |  | ||||||
| 			//					if !stateExists(currentStates, nextMatch) { |  | ||||||
| 			//						currentStates = append(currentStates, nextMatch) |  | ||||||
| 			//					} |  | ||||||
| 			//				} else if currentState.isEmpty && !currentState.groupBegin && !currentState.groupEnd { |  | ||||||
| 			//					if !stateExists(currentStates, nextMatch) { |  | ||||||
| 			//						currentStates = append(currentStates, nextMatch) |  | ||||||
| 			//					} |  | ||||||
| 			//				} else { |  | ||||||
| 			//					if !stateExists(nextStates, nextMatch) { |  | ||||||
| 			//						nextStates = append(nextStates, nextMatch) |  | ||||||
| 			//					} |  | ||||||
| 			//				} |  | ||||||
| 			//			} |  | ||||||
| 			// |  | ||||||
| 			//			if currentState.isLast && len(nextStates) == 0 { // Last state reached |  | ||||||
| 			//				currentState.threadGroups[0].EndIdx = idx |  | ||||||
| 			//				if idx == currentState.threadGroups[0].StartIdx { |  | ||||||
| 			//					idx += 1 |  | ||||||
| 			//				} |  | ||||||
| 			//				return true, currentState.threadGroups, idx |  | ||||||
| 			//			} |  | ||||||
| 		} | 		} | ||||||
| 		currentStates = append([]nfaState{}, nextStates...) | 		currentStates = append([]nfaState{}, nextStates...) | ||||||
| 		nextStates = nil | 		nextStates = nil | ||||||
| @@ -431,196 +301,4 @@ func findAllSubmatchHelper(start *nfaState, str []rune, offset int, numGroups in | |||||||
| 		return true, match, match[0].EndIdx | 		return true, match, match[0].EndIdx | ||||||
| 	} | 	} | ||||||
| 	return false, []Group{}, i + 1 | 	return false, []Group{}, i + 1 | ||||||
| 	//		zeroStates := make([]*nfaState, 0) |  | ||||||
| 	//		// Keep taking zero-states, until there are no more left to take |  | ||||||
| 	//		// Objective: If any of our current states have transitions to 0-states, replace them with the 0-state. Do this until there are no more transitions to 0-states, or there are no more unique 0-states to take. |  | ||||||
| 	//		topStateItem := currentStates.peek() |  | ||||||
| 	//		topState := topStateItem.(*priorQueueItem).state |  | ||||||
| 	//		zeroStates, isZero := takeZeroState([]*nfaState{topState}, numGroups, i) |  | ||||||
| 	//		tempStates = append(tempStates, zeroStates...) |  | ||||||
| 	//		num_appended := 0 |  | ||||||
| 	//		for isZero == true { |  | ||||||
| 	//			zeroStates, isZero = takeZeroState(tempStates, numGroups, i) |  | ||||||
| 	//			tempStates, num_appended = uniqueAppend(tempStates, zeroStates...) |  | ||||||
| 	//			if num_appended == 0 { // Break if we haven't appended any more unique values |  | ||||||
| 	//				break |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	//		if isZero == true { |  | ||||||
| 	//			currentStates.Pop() |  | ||||||
| 	//		} |  | ||||||
| 	// |  | ||||||
| 	//		for _, state := range tempStates { |  | ||||||
| 	//			heap.Push(currentStates, newPriorQueueItem(state)) |  | ||||||
| 	//		} |  | ||||||
| 	//		tempStates = nil |  | ||||||
| 	// |  | ||||||
| 	//		// Take any transitions corresponding to current character |  | ||||||
| 	//		numStatesMatched := 0            // The number of states which had at least 1 match for this round |  | ||||||
| 	//		assertionFailed := false         // Whether or not an assertion failed for this round |  | ||||||
| 	//		lastStateInList := false         // Whether or not a last state was in our list of states |  | ||||||
| 	//		var lastStatePtr *nfaState = nil // Pointer to the last-state, if it was found |  | ||||||
| 	//		lastLookaroundInList := false    // Whether or not a last state (that is a lookaround) was in our list of states |  | ||||||
| 	//		for numStatesMatched == 0 && lastStateInList == false { |  | ||||||
| 	//			if currentStates.Len() == 0 { |  | ||||||
| 	//				break |  | ||||||
| 	//			} |  | ||||||
| 	//			stateItem := heap.Pop(currentStates) |  | ||||||
| 	//			state := stateItem.(*priorQueueItem).state |  | ||||||
| 	//			matches, numMatches := state.matchesFor(str, i) |  | ||||||
| 	//			if numMatches > 0 { |  | ||||||
| 	//				numStatesMatched++ |  | ||||||
| 	//				tempStates = append([]*nfaState(nil), matches...) |  | ||||||
| 	//				foundPath = true |  | ||||||
| 	//				for _, m := range matches { |  | ||||||
| 	//					if m.threadGroups == nil { |  | ||||||
| 	//						m.threadGroups = newMatch(numGroups + 1) |  | ||||||
| 	//					} |  | ||||||
| 	//					m.threadSP = state.threadSP + 1 |  | ||||||
| 	//					copy(m.threadGroups, state.threadGroups) |  | ||||||
| 	//				} |  | ||||||
| 	//			} |  | ||||||
| 	//			if numMatches < 0 { |  | ||||||
| 	//				assertionFailed = true |  | ||||||
| 	//			} |  | ||||||
| 	//			if state.isLast { |  | ||||||
| 	//				if state.isLookaround() { |  | ||||||
| 	//					lastLookaroundInList = true |  | ||||||
| 	//				} |  | ||||||
| 	//				lastStateInList = true |  | ||||||
| 	//				lastStatePtr = state |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	// |  | ||||||
| 	//		if assertionFailed && numStatesMatched == 0 { // Nothing has matched and an assertion has failed |  | ||||||
| 	//			// If I'm being completely honest, I'm not sure why I have to check specifically for a _lookaround_ |  | ||||||
| 	//			// state. The explanation below is my attempt to explain this behavior. |  | ||||||
| 	//			// If you replace 'lastLookaroundInList' with 'lastStateInList', one of the test cases fails. |  | ||||||
| 	//			// |  | ||||||
| 	//			// One of the states in our list was a last state and a lookaround. In this case, we |  | ||||||
| 	//			// don't abort upon failure of the assertion, because we have found |  | ||||||
| 	//			// another path to a final state. |  | ||||||
| 	//			// Even if the last state _was_ an assertion, we can use the previously |  | ||||||
| 	//			// saved indices to find a match. |  | ||||||
| 	//			if lastLookaroundInList { |  | ||||||
| 	//				break |  | ||||||
| 	//			} else { |  | ||||||
| 	//				if i == startingFrom { |  | ||||||
| 	//					i++ |  | ||||||
| 	//				} |  | ||||||
| 	//				return false, []Group{}, i |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	//		// Check if we can find a state in our list that is: |  | ||||||
| 	//		// 	a. A last-state |  | ||||||
| 	//		// 	b. Empty |  | ||||||
| 	//		// 	c. Doesn't assert anything |  | ||||||
| 	//		for _, stateItem := range *currentStates { |  | ||||||
| 	//			s := stateItem.state |  | ||||||
| 	//			if s.isLast && s.isEmpty && s.assert == noneAssert { |  | ||||||
| 	//				lastStatePtr = s |  | ||||||
| 	//				lastStateInList = true |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	//		if lastStateInList && numStatesMatched == 0 { // A last-state was in the list of states. add the matchIndex to our MatchIndex list |  | ||||||
| 	//			for j := 1; j < numGroups+1; j++ { |  | ||||||
| 	//				tempIndices[j] = lastStatePtr.threadGroups[j] |  | ||||||
| 	//			} |  | ||||||
| 	//			endIdx = i |  | ||||||
| 	//			tempIndices[0] = Group{startIdx, endIdx} |  | ||||||
| 	//			if tempIndices[0].StartIdx == tempIndices[0].EndIdx { |  | ||||||
| 	//				return true, tempIndices, tempIndices[0].EndIdx + 1 |  | ||||||
| 	//			} else { |  | ||||||
| 	//				return true, tempIndices, tempIndices[0].EndIdx |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	// |  | ||||||
| 	//		// Check if we can find a zero-length match |  | ||||||
| 	//		if foundPath == false { |  | ||||||
| 	//			currentStatesList := funcMap(*currentStates, func(item *priorQueueItem) *nfaState { |  | ||||||
| 	//				return item.state |  | ||||||
| 	//			}) |  | ||||||
| 	//			if ok := zeroMatchPossible(str, i, numGroups, currentStatesList...); ok { |  | ||||||
| 	//				if tempIndices[0].IsValid() == false { |  | ||||||
| 	//					tempIndices[0] = Group{startIdx, startIdx} |  | ||||||
| 	//				} |  | ||||||
| 	//			} |  | ||||||
| 	//			// If we haven't moved in the string, increment the counter by 1 |  | ||||||
| 	//			// to ensure we don't keep trying the same string over and over. |  | ||||||
| 	//			//			if i == startingFrom { |  | ||||||
| 	//			startIdx++ |  | ||||||
| 	//			//	i++ |  | ||||||
| 	//			//			} |  | ||||||
| 	//			if tempIndices.numValidGroups() > 0 && tempIndices[0].IsValid() { |  | ||||||
| 	//				if tempIndices[0].StartIdx == tempIndices[0].EndIdx { // If we have a zero-length match, we have to shift the index at which we start. Otherwise we keep looking at the same paert of the string over and over. |  | ||||||
| 	//					return true, tempIndices, tempIndices[0].EndIdx + 1 |  | ||||||
| 	//				} else { |  | ||||||
| 	//					return true, tempIndices, tempIndices[0].EndIdx |  | ||||||
| 	//				} |  | ||||||
| 	//			} |  | ||||||
| 	//			return false, []Group{}, startIdx |  | ||||||
| 	//		} |  | ||||||
| 	//		currentStates = &priorityQueue{} |  | ||||||
| 	//		slices.Reverse(tempStates) |  | ||||||
| 	//		for _, state := range tempStates { |  | ||||||
| 	//			heap.Push(currentStates, newPriorQueueItem(state)) |  | ||||||
| 	//		} |  | ||||||
| 	//		tempStates = nil |  | ||||||
| 	// |  | ||||||
| 	//		i++ |  | ||||||
| 	//	} |  | ||||||
| 	// |  | ||||||
| 	// // End-of-string reached. Go to any 0-states, until there are no more 0-states to go to. Then check if any of our states are in the end position. |  | ||||||
| 	// // This is the exact same algorithm used inside the loop, so I should probably put it in a function. |  | ||||||
| 	// |  | ||||||
| 	//	if currentStates.Len() > 0 { |  | ||||||
| 	//		topStateItem := currentStates.peek() |  | ||||||
| 	//		topState := topStateItem.(*priorQueueItem).state |  | ||||||
| 	//		zeroStates, isZero := takeZeroState([]*nfaState{topState}, numGroups, i) |  | ||||||
| 	//		tempStates = append(tempStates, zeroStates...) |  | ||||||
| 	//		num_appended := 0 // Number of unique states addded to tempStates |  | ||||||
| 	//		for isZero == true { |  | ||||||
| 	//			zeroStates, isZero = takeZeroState(tempStates, numGroups, i) |  | ||||||
| 	//			tempStates, num_appended = uniqueAppend(tempStates, zeroStates...) |  | ||||||
| 	//			if num_appended == 0 { // Break if we haven't appended any more unique values |  | ||||||
| 	//				break |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	//	} |  | ||||||
| 	// |  | ||||||
| 	//	for _, state := range tempStates { |  | ||||||
| 	//		heap.Push(currentStates, newPriorQueueItem(state)) |  | ||||||
| 	//	} |  | ||||||
| 	// |  | ||||||
| 	// tempStates = nil |  | ||||||
| 	// |  | ||||||
| 	//	for _, stateItem := range *currentStates { |  | ||||||
| 	//		state := stateItem.state |  | ||||||
| 	//		// Only add the match if the start index is in bounds. If the state has an assertion, |  | ||||||
| 	//		// make sure the assertion checks out. |  | ||||||
| 	//		if state.isLast && i <= len(str) { |  | ||||||
| 	//			if state.assert == noneAssert || state.checkAssertion(str, i) { |  | ||||||
| 	//				for j := 1; j < numGroups+1; j++ { |  | ||||||
| 	//					tempIndices[j] = state.threadGroups[j] |  | ||||||
| 	//				} |  | ||||||
| 	//				endIdx = i |  | ||||||
| 	//				tempIndices[0] = Group{startIdx, endIdx} |  | ||||||
| 	//			} |  | ||||||
| 	//		} |  | ||||||
| 	//	} |  | ||||||
| 	// |  | ||||||
| 	//	if tempIndices.numValidGroups() > 0 { |  | ||||||
| 	//		if tempIndices[0].StartIdx == tempIndices[0].EndIdx { // If we have a zero-length match, we have to shift the index at which we start. Otherwise we keep looking at the same paert of the string over and over. |  | ||||||
| 	//			return true, tempIndices, tempIndices[0].EndIdx + 1 |  | ||||||
| 	//		} else { |  | ||||||
| 	//			return true, tempIndices, tempIndices[0].EndIdx |  | ||||||
| 	//		} |  | ||||||
| 	//	} |  | ||||||
| 	// |  | ||||||
| 	// if startIdx == startingFrom { // Increment starting index if we haven't moved in the string. Prevents us from matching the same part of the string over and over. |  | ||||||
| 	// |  | ||||||
| 	//		startIdx++ |  | ||||||
| 	//	} |  | ||||||
| 	// |  | ||||||
| 	// return false, []Group{}, startIdx |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user