/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.regexp; import java.util.BitSet; import java.util.Iterator; import java.util.regex.MatchResult; import org.joni.Matcher; import org.joni.Option; import org.joni.Regex; import org.joni.Region; /** * {@link MatchState} implementation for Joni {@link Regex} regular expressions */ final class JoniMatchState implements MatchState, IterableMatchResult { private final UEncoding encoding; private final Matcher matcher; private final CharSequence string; private final BitSet negativeLAGroups; private final StringPosition position; private int begin = -1, end = 0; private Region region; public JoniMatchState(UEncoding encoding, Matcher matcher, CharSequence string, BitSet negativeLAGroups) { this.encoding = encoding; this.matcher = matcher; this.string = string; this.negativeLAGroups = negativeLAGroups; this.position = new StringPosition(encoding.length(string)); } private JoniMatchState(UEncoding encoding, CharSequence string, BitSet negativeLAGroups, StringPosition position, int begin, int end, Region region) { this.encoding = encoding; this.matcher = null; this.string = string; this.negativeLAGroups = negativeLAGroups; this.position = position; this.begin = begin; this.end = end; this.region = region; } private final class StringPosition implements Cloneable { final int length; int begin, end, stringBegin, stringEnd; StringPosition(int length) { this.length = length; } @Override public StringPosition clone() { StringPosition clone = new StringPosition(length); clone.begin = begin; clone.end = end; clone.stringBegin = stringBegin; clone.stringEnd = stringEnd; return clone; } void update(int begin, int end) { assert begin >= 0 && end >= 0 && begin <= end; this.stringBegin = stringIndex(begin); this.stringEnd = stringIndex(end); this.begin = begin; this.end = end; } /** * Returns the string index for a given byte index. * * @param byteIndex * the byte index * @return the string index */ int stringIndex(int byteIndex) { if (byteIndex >= end) { return stringEnd + encoding.strLength(string, stringEnd, byteIndex - end); } if (byteIndex >= begin) { return stringBegin + encoding.strLength(string, stringBegin, byteIndex - begin); } return encoding.strLength(string, 0, byteIndex); } /** * Returns the byte index for a given string index. * * @param stringIndex * the string index * @return the byte index */ int byteIndex(int stringIndex) { if (stringIndex >= stringEnd) { return end + encoding.length(string, stringEnd, stringIndex); } if (stringIndex >= stringBegin) { return begin + encoding.length(string, stringBegin, stringIndex); } return encoding.length(string, 0, stringIndex); } } private boolean update(int r) { begin = matcher.getBegin(); end = matcher.getEnd(); region = matcher.getRegion(); position.update(begin, end); return r > Matcher.FAILED; } private boolean isUnicode() { return !(encoding instanceof UCS2Encoding); } private int stringLength() { return string.length(); } private int byteLength() { return position.length; } private int toStringIndex(int byteIndex) { if (byteIndex < 0) { return -1; } return position.stringIndex(byteIndex); } private int toByteIndex(int stringIndex) { return position.byteIndex(stringIndex); } private void ensureResult() { if (begin < 0) throw new IllegalStateException("No match!"); } private void ensureValidIndex(int index) { if (index < 0 || index > stringLength()) throw new IndexOutOfBoundsException("Invalid index: " + index); } private void ensureValidGroup(int group) { if (group < 0 || group > groupCount()) throw new IndexOutOfBoundsException("Invalid group: " + group); } private int toValidStartIndex(int start) { // Don't start matching in middle of a surrogate pair. if (start > 0 && Character.isSupplementaryCodePoint(Character.codePointAt(string, start - 1)) && isUnicode()) { return start - 1; } return start; } @Override public String toString() { return String.format("%s: [string=%s, begin=%d, end=%d]", getClass().getSimpleName(), string, begin, end); } @Override public Iterator<String> iterator() { return new GroupIterator(this, negativeLAGroups); } @Override public MatchResult toMatchResult() { return new JoniMatchState(encoding, string, negativeLAGroups, position.clone(), begin, end, region != null ? region.clone() : null); } @Override public boolean find(int start) { ensureValidIndex(start); int actualStart = toValidStartIndex(start); return update(matcher.search(toByteIndex(actualStart), byteLength(), Option.NONE)); } @Override public boolean matches(int start) { ensureValidIndex(start); int actualStart = toValidStartIndex(start); return update(matcher.match(toByteIndex(actualStart), byteLength(), Option.NONE)); } @Override public int start() { ensureResult(); return position.stringBegin; } @Override public int start(int group) { ensureResult(); ensureValidGroup(group); if (group == 0) { return position.stringBegin; } return toStringIndex(region.beg[group]); } @Override public int end() { ensureResult(); return position.stringEnd; } @Override public int end(int group) { ensureResult(); ensureValidGroup(group); if (group == 0) { return position.stringEnd; } return toStringIndex(region.end[group]); } @Override public String group() { return group(0); } @Override public String group(int group) { int start = start(group), end = end(group); if (start == -1 || end == -1) { return null; } return string.subSequence(start, end).toString(); } @Override public int groupCount() { Region region = this.region; return region != null ? region.numRegs - 1 : 0; } }