diff --git a/core/src/main/java/io/github/mmm/scanner/CharReaderScanner.java b/core/src/main/java/io/github/mmm/scanner/CharReaderScanner.java index 277a3f5..6b8189d 100644 --- a/core/src/main/java/io/github/mmm/scanner/CharReaderScanner.java +++ b/core/src/main/java/io/github/mmm/scanner/CharReaderScanner.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.Reader; +import io.github.mmm.base.filter.CharFilter; import io.github.mmm.base.text.TextFormatMessageHandler; /** @@ -154,6 +155,47 @@ public String peekString(int count) { } } + @Override + public String peekWhile(CharFilter filter, int maxLen) { + + if (!hasNext()) { + return ""; + } + int rest = this.limit - this.offset; + if (rest > maxLen) { + rest = maxLen; + } + int len = 0; + while (len < rest) { + char c = this.buffer[this.offset + len]; + if (!filter.accept(c)) { + return new String(this.buffer, this.offset, len); + } + len++; + } + if (fillLookahead()) { + int fullRest = rest + this.lookaheadLimit; + if ((maxLen > fullRest) && !isEos()) { + throwLookaheadError(maxLen); + } + len = 0; + int end = maxLen - rest; + while (len < end) { + char c = this.lookaheadBuffer[len]; + if (!filter.accept(c)) { + break; + } + len++; + } + StringBuilder sb = new StringBuilder(rest + len); + sb.append(this.buffer, this.offset, rest); + sb.append(this.lookaheadBuffer, 0, len); + return sb.toString(); + } else { + return new String(this.buffer, this.offset, rest); + } + } + @Override public String getBufferToParse() { @@ -408,4 +450,12 @@ protected boolean expectRestWithLookahead(char[] stopChars, boolean ignoreCase, return true; } + @Override + protected void reset() { + + super.reset(); + this.lookaheadLimit = 0; + this.position = 0; + } + } diff --git a/core/src/main/java/io/github/mmm/scanner/CharSequenceScanner.java b/core/src/main/java/io/github/mmm/scanner/CharSequenceScanner.java index 3967abd..5ae93ce 100644 --- a/core/src/main/java/io/github/mmm/scanner/CharSequenceScanner.java +++ b/core/src/main/java/io/github/mmm/scanner/CharSequenceScanner.java @@ -289,6 +289,34 @@ public String peekString(int count) { return result; } + @Override + public String peekWhile(CharFilter filter, int maxLen) { + + if (maxLen < 0) { + throw new IllegalArgumentException("Max must NOT be negative: " + maxLen); + } + int len = 0; + int end = this.offset + maxLen; + if (end < 0) { // overflow? + end = maxLen; + } + if (end > this.limit) { + end = this.limit; + } + while (len < end) { + char c = this.buffer[len]; + if (!filter.accept(c)) { + break; + } + len++; + } + if (len == 0) { + return ""; + } else { + return new String(this.buffer, this.offset, len); + } + } + @Override public String readUntil(CharFilter filter, boolean acceptEot) { diff --git a/core/src/main/java/io/github/mmm/scanner/CharStreamScanner.java b/core/src/main/java/io/github/mmm/scanner/CharStreamScanner.java index fd49fac..0514388 100644 --- a/core/src/main/java/io/github/mmm/scanner/CharStreamScanner.java +++ b/core/src/main/java/io/github/mmm/scanner/CharStreamScanner.java @@ -80,6 +80,17 @@ public interface CharStreamScanner extends TextFormatProcessor { */ String peekString(int count); + /** + * @param filter the {@link CharFilter} {@link CharFilter#accept(char) accepting} only the characters to peek. + * @param maxLen the maximum number of characters to peek (get as lookahead without modifying this stream). + * @return a {@link String} with the {@link #peek() peeked} characters of the given {@code maxLen} or less if a + * character was hit that is not {@link CharFilter#accept(char) accepted} by the given {@code filter} + * or the end-of-text has been reached before. The state of this stream remains unchanged. + * @see #readWhile(CharFilter) + * @see #skip(int) + */ + String peekWhile(CharFilter filter, int maxLen); + /** * This method reads the number of {@link #next() next characters} given by {@code count} and returns them as string. * If there are less characters {@link #hasNext() available} the returned string will be shorter than {@code count} diff --git a/core/src/test/java/io/github/mmm/scanner/AbstractCharStreamScannerTest.java b/core/src/test/java/io/github/mmm/scanner/AbstractCharStreamScannerTest.java index 3c73b91..d59c52a 100644 --- a/core/src/test/java/io/github/mmm/scanner/AbstractCharStreamScannerTest.java +++ b/core/src/test/java/io/github/mmm/scanner/AbstractCharStreamScannerTest.java @@ -712,6 +712,28 @@ public void testReadWhile() { assertThat(scanner.getLine()).isEqualTo(1); } + @Test + public void testPeekWhile() { + + // given + String string = "abc def ghi"; + CharFilter textFilter = CharFilter.LATIN_LETTER; + CharFilter spaceFilter = CharFilter.WHITESPACE; + // when + CharStreamScanner scanner = scanner(string, 3); + // then + assertThat(scanner.peekWhile(textFilter, 3)).isEqualTo("abc"); + scanner.skip(3); + assertThat(scanner.peekWhile(textFilter, 3)).isEmpty(); + scanner.skip(1); + assertThat(scanner.peekWhile(textFilter, 3)).isEqualTo("def"); + scanner.skip(3); + assertThat(scanner.peekWhile(textFilter, 3)).isEmpty(); + scanner.skip(2); + assertThat(scanner.peekWhile(textFilter, 2)).isEqualTo("gh"); + assertThat(scanner.peekWhile(textFilter, 3)).isEqualTo("ghi"); + } + @Test public void testReadLine() {