SimpleTextParserTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.io.core.internal;
import java.io.Reader;
import java.io.StringReader;
import java.util.function.IntPredicate;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class SimpleTextParserTest {
private static final double EPS = 1e-20;
private static final int EOF = -1;
@Test
void testMaxStringLength_defaultValue() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
Assertions.assertEquals(1024, p.getMaxStringLength());
}
@Test
void testMaxStringLength_illegalArg() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.setMaxStringLength(-1);
}, IllegalArgumentException.class, "Maximum string length cannot be less than zero; was -1");
}
@Test
void testCharacterSequence() {
// act/assert
assertCharacterSequence(parser(""), "");
assertCharacterSequence(parser("abc def"), "abc def");
}
@Test
void testCharacterPosition() {
// arrange
final SimpleTextParser p = parser(
"a b\n" +
"\r\n" +
"d \r" +
"e");
// act/assert
assertPosition(p, 1, 1);
assertChar('a', p.readChar());
assertPosition(p, 1, 2);
assertChar(' ', p.readChar());
assertPosition(p, 1, 3);
assertChar('b', p.readChar());
assertPosition(p, 1, 4);
assertChar('\n', p.readChar());
assertPosition(p, 2, 1);
assertChar('\r', p.readChar());
assertPosition(p, 2, 2);
assertChar('\n', p.readChar());
assertPosition(p, 3, 1);
assertChar('d', p.readChar());
assertPosition(p, 3, 2);
assertChar(' ', p.readChar());
assertPosition(p, 3, 3);
assertChar('\r', p.readChar());
assertPosition(p, 4, 1);
assertChar('e', p.readChar());
assertPosition(p, 4, 2);
assertChar(EOF, p.readChar());
}
@Test
void testCharacterPosition_givenPosition() {
// arrange
final SimpleTextParser p = parser("abc\rdef");
// act/assert
assertPosition(p, 1, 1);
p.setLineNumber(10);
p.setColumnNumber(3);
assertPosition(p, 10, 3);
p.discard(4);
assertPosition(p, 11, 1);
p.discard(3);
assertPosition(p, 11, 4);
}
@Test
void testHasMoreCharacters() {
// arrange
final SimpleTextParser empty = parser("");
final SimpleTextParser nonEmpty = parser("a");
// act/assert
Assertions.assertFalse(empty.hasMoreCharacters());
Assertions.assertTrue(nonEmpty.hasMoreCharacters());
assertChar('a', nonEmpty.readChar());
Assertions.assertFalse(nonEmpty.hasMoreCharacters());
}
@Test
void testHasMoreCharactersOnLine() {
// arrange
final SimpleTextParser empty = parser("");
final SimpleTextParser singleLine = parser("a");
final SimpleTextParser multiLine = parser("a\r\nb\rc\n\n");
// act/assert
Assertions.assertFalse(empty.hasMoreCharactersOnLine());
Assertions.assertTrue(singleLine.hasMoreCharactersOnLine());
assertChar('a', singleLine.readChar());
Assertions.assertFalse(singleLine.hasMoreCharactersOnLine());
Assertions.assertTrue(multiLine.hasMoreCharactersOnLine());
assertChar('a', multiLine.readChar());
Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
assertChar('\r', multiLine.readChar());
Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
assertChar('\n', multiLine.readChar());
Assertions.assertTrue(multiLine.hasMoreCharactersOnLine());
assertChar('b', multiLine.readChar());
Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
assertChar('\r', multiLine.readChar());
Assertions.assertTrue(multiLine.hasMoreCharactersOnLine());
assertChar('c', multiLine.readChar());
Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
assertChar('\n', multiLine.readChar());
Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
assertChar('\n', multiLine.readChar());
Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
assertChar(EOF, multiLine.readChar());
}
@Test
void testBasicTokenMethods() {
// arrange
final SimpleTextParser p = parser("abcdef\r\n\r ghi");
// act/assert
assertToken(p, null, -1, -1);
Assertions.assertFalse(p.hasNonEmptyToken());
assertToken(p.next(1), "a", 1, 1);
Assertions.assertTrue(p.hasNonEmptyToken());
assertToken(p.next(3), "bcd", 1, 2);
Assertions.assertTrue(p.hasNonEmptyToken());
assertToken(p.next(5), "ef\r\n\r", 1, 5);
Assertions.assertTrue(p.hasNonEmptyToken());
assertToken(p.next(0), "", 3, 1);
Assertions.assertFalse(p.hasNonEmptyToken());
assertToken(p.next(1), " ", 3, 1);
Assertions.assertTrue(p.hasNonEmptyToken());
assertToken(p.next(3), "ghi", 3, 2);
Assertions.assertTrue(p.hasNonEmptyToken());
assertToken(p.next(1), null, 3, 5);
Assertions.assertFalse(p.hasNonEmptyToken());
}
@Test
void testGetCurrentTokenAsDouble() {
// arrange
final SimpleTextParser p = parser("1e-4\n+5\n-4.001");
// act/assert
p.nextLine();
Assertions.assertEquals(1e-4, p.getCurrentTokenAsDouble(), EPS);
p.nextLine();
Assertions.assertEquals(5.0, p.getCurrentTokenAsDouble(), EPS);
p.nextLine();
Assertions.assertEquals(-4.001, p.getCurrentTokenAsDouble(), EPS);
}
@Test
void testGetCurrentTokenAsDouble_failures() {
// arrange
final SimpleTextParser p = parser("abc\n1.1.1a");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class, "No token has been read from the character stream");
p.next(SimpleTextParser::isNotNewLinePart);
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class,
"Parsing failed at line 1, column 1: expected double but found [abc]");
p.nextAlphanumeric();
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class,
"Parsing failed at line 1, column 4: expected double but found end of line");
p.discardLine()
.next(c -> c != 'a');
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class,
"Parsing failed at line 2, column 1: expected double but found [1.1.1]");
p.next(Character::isDigit);
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class,
"Parsing failed at line 2, column 6: expected double but found empty token followed by [a]");
p.nextLine();
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class,
"Parsing failed at line 2, column 6: expected double but found [a]");
p.nextLine();
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsDouble,
IllegalStateException.class,
"Parsing failed at line 2, column 7: expected double but found end of content");
}
@Test
void testGetCurrentTokenAsDouble_includedNumberFormatExceptionOnFailure() {
// arrange
final SimpleTextParser p = parser("abc");
p.nextLine();
// act/assert
final Throwable exc = Assertions.assertThrows(IllegalStateException.class, p::getCurrentTokenAsDouble);
Assertions.assertEquals(NumberFormatException.class, exc.getCause().getClass());
}
@Test
void testGetCurrentTokenAsInt() {
// arrange
final SimpleTextParser p = parser("0\n+5\n-401");
// act/assert
p.nextLine();
Assertions.assertEquals(0, p.getCurrentTokenAsInt());
p.nextLine();
Assertions.assertEquals(5, p.getCurrentTokenAsInt());
p.nextLine();
Assertions.assertEquals(-401, p.getCurrentTokenAsInt());
}
@Test
void testGetCurrentTokenAsInt_failures() {
// arrange
final SimpleTextParser p = parser("abc\n1.1.1a");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class, "No token has been read from the character stream");
p.next(SimpleTextParser::isNotNewLinePart);
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class,
"Parsing failed at line 1, column 1: expected integer but found [abc]");
p.nextAlphanumeric();
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class,
"Parsing failed at line 1, column 4: expected integer but found end of line");
p.discardLine()
.next(c -> c != 'a');
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class,
"Parsing failed at line 2, column 1: expected integer but found [1.1.1]");
p.next(Character::isDigit);
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class,
"Parsing failed at line 2, column 6: expected integer but found empty token followed by [a]");
p.nextLine();
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class,
"Parsing failed at line 2, column 6: expected integer but found [a]");
p.nextLine();
GeometryTestUtils.assertThrowsWithMessage(p::getCurrentTokenAsInt,
IllegalStateException.class,
"Parsing failed at line 2, column 7: expected integer but found end of content");
}
@Test
void testGetCurrentTokenAsInt_includedNumberFormatExceptionOnFailure() {
// arrange
final SimpleTextParser p = parser("abc");
p.nextLine();
// act/assert
final Throwable exc = Assertions.assertThrows(IllegalStateException.class, p::getCurrentTokenAsInt);
Assertions.assertEquals(NumberFormatException.class, exc.getCause().getClass());
}
@Test
void testNext_lenArg() {
// arrange
final SimpleTextParser p = parser("abcdef\r\n\r ghi");
// act/assert
assertToken(p.next(0), "", 1, 1);
assertToken(p.next(4), "abcd", 1, 1);
assertToken(p.next(6), "ef\r\n\r ", 1, 5);
assertToken(p.next(100), "ghi", 3, 2);
assertToken(p.next(0), null, 3, 5);
assertToken(p.next(100), null, 3, 5);
}
@Test
void testNextWithLineContinuation_lenArg() {
// arrange
final char cont = '\\';
final SimpleTextParser p = parser("a\\bcdef\\\r\n\r ghi\\\n\\\n\\\rj");
// act/assert
assertToken(p.nextWithLineContinuation(cont, 0), "", 1, 1);
assertToken(p.nextWithLineContinuation(cont, 5), "a\\bcd", 1, 1);
assertToken(p.nextWithLineContinuation(cont, 3), "ef\r", 1, 6);
assertToken(p.nextWithLineContinuation(cont, 100), " ghij", 3, 1);
assertToken(p.nextWithLineContinuation(cont, 0), null, 6, 2);
assertToken(p.nextWithLineContinuation(cont, 100), null, 6, 2);
}
@Test
void testNext_lenArg_invalidArg() {
// arrange
final SimpleTextParser p = parser("abc");
p.setMaxStringLength(2);
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.next(-1);
}, IllegalArgumentException.class, "Requested string length cannot be negative; was -1");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.next(3);
}, IllegalArgumentException.class, "Requested string length of 3 exceeds maximum value of 2");
}
@Test
void testNext_predicateArg() {
// arrange
final SimpleTextParser p = parser("a\n 012\r\ndef");
// act/assert
assertToken(p.next(c -> false), "", 1, 1);
assertToken(p.next(Character::isAlphabetic), "a", 1, 1);
assertToken(p.next(Character::isAlphabetic), "", 1, 2);
assertToken(p.next(Character::isWhitespace), "\n ", 1, 2);
assertToken(p.next(Character::isWhitespace), "", 2, 2);
assertToken(p.next(Character::isDigit), "012", 2, 2);
assertToken(p.next(Character::isDigit), "", 2, 5);
assertToken(p.next(Character::isWhitespace), "\r\n", 2, 5);
assertToken(p.next(Character::isWhitespace), "", 3, 1);
assertToken(p.next(c -> true), "def", 3, 1);
assertToken(p.next(c -> true), null, 3, 4);
}
@Test
void testNext_predicateArg_exceedsMaxStringLength() {
// arrange
final SimpleTextParser p = parser("abcdef");
p.setMaxStringLength(4);
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.next(c -> !Character.isWhitespace(c));
}, IllegalStateException.class, "Parsing failed at line 1, column 1: string length exceeds maximum value of 4");
}
@Test
void testNextWithLineContinuation_predicateArg() {
// arrange
final char cont = '|';
final SimpleTextParser p = parser("|\na\n 0|\r\n|\r12\r\nd|ef");
// act/assert
assertToken(p.nextWithLineContinuation(cont, c -> false), "", 1, 1);
assertToken(p.nextWithLineContinuation(cont, Character::isAlphabetic), "a", 2, 1);
assertToken(p.nextWithLineContinuation(cont, Character::isAlphabetic), "", 2, 2);
assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "\n ", 2, 2);
assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "", 3, 2);
assertToken(p.nextWithLineContinuation(cont, Character::isDigit), "012", 3, 2);
assertToken(p.nextWithLineContinuation(cont, Character::isDigit), "", 5, 3);
assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "\r\n", 5, 3);
assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "", 6, 1);
assertToken(p.nextWithLineContinuation(cont, c -> true), "d|ef", 6, 1);
assertToken(p.nextWithLineContinuation(cont, c -> true), null, 6, 5);
}
@Test
void testNextLine() {
// arrange
final SimpleTextParser p = parser("a\n 012\r\ndef\n\nx");
// act/assert
assertToken(p.nextLine(), "a", 1, 1);
assertToken(p.nextLine(), " 012", 2, 1);
p.readChar();
assertToken(p.nextLine(), "ef", 3, 2);
assertToken(p.nextLine(), "", 4, 1);
assertToken(p.nextLine(), "x", 5, 1);
assertToken(p.nextLine(), null, 5, 2);
}
@Test
void testNextAlphanumeric() {
// arrange
final SimpleTextParser p = parser("a10Fd;X23456789-0\ny");
// act/assert
assertToken(p.nextAlphanumeric(), "a10Fd", 1, 1);
assertChar(';', p.readChar());
assertToken(p.nextAlphanumeric(), "X23456789", 1, 7);
assertChar('-', p.readChar());
assertToken(p.nextAlphanumeric(), "0", 1, 17);
assertToken(p.nextAlphanumeric(), "", 1, 18);
assertChar('\n', p.readChar());
assertToken(p.nextAlphanumeric(), "y", 2, 1);
assertToken(p.nextAlphanumeric(), null, 2, 2);
}
@Test
void testDiscard_lenArg() {
// arrange
final SimpleTextParser p = parser("\na,b c\r\n12.3\rdef\n");
// act/assert
p.discard(0);
assertChar('\n', p.peekChar());
assertPosition(p, 1, 1);
p.discard(1);
assertChar('a', p.peekChar());
assertPosition(p, 2, 1);
p.discard(8);
assertChar('2', p.peekChar());
assertPosition(p, 3, 2);
p.discard(100);
assertChar(EOF, p.peekChar());
assertPosition(p, 5, 1);
p.discard(0);
assertChar(EOF, p.peekChar());
assertPosition(p, 5, 1);
p.discard(100);
assertChar(EOF, p.peekChar());
assertPosition(p, 5, 1);
}
@Test
void testDiscardWithLineContinuation_lenArg() {
// arrange
final char cont = '|';
final SimpleTextParser p = parser("\n|a|\r\n,b|\n|\r c\r\n12.3\rdef\n");
// act/assert
p.discardWithLineContinuation(cont, 0);
assertChar('\n', p.peekChar());
assertPosition(p, 1, 1);
p.discardWithLineContinuation(cont, 1);
assertChar('|', p.peekChar());
assertPosition(p, 2, 1);
p.discardWithLineContinuation(cont, 8);
assertChar('1', p.peekChar());
assertPosition(p, 6, 1);
p.discardWithLineContinuation(cont, 100);
assertChar(EOF, p.peekChar());
assertPosition(p, 8, 1);
p.discardWithLineContinuation(cont, 0);
assertChar(EOF, p.peekChar());
assertPosition(p, 8, 1);
p.discardWithLineContinuation(cont, 100);
assertChar(EOF, p.peekChar());
assertPosition(p, 8, 1);
}
@Test
void testDiscard_predicateArg() {
// arrange
final SimpleTextParser p = parser("\na,b c\r\n12.3\rdef\n");
// act/assert
p.discard(Character::isWhitespace);
assertChar('a', p.peekChar());
assertPosition(p, 2, 1);
p.discard(c -> !Character.isWhitespace(c));
assertChar(' ', p.peekChar());
assertPosition(p, 2, 4);
p.discard(Character::isDigit); // should not advance
assertChar(' ', p.peekChar());
assertPosition(p, 2, 4);
p.discard(Character::isWhitespace);
assertChar('c', p.peekChar());
assertPosition(p, 2, 5);
p.discard(c -> c != 'd');
assertChar('d', p.peekChar());
assertPosition(p, 4, 1);
p.discard(c -> true);
assertChar(EOF, p.peekChar());
assertPosition(p, 5, 1);
p.discard(c -> true);
assertChar(EOF, p.peekChar());
assertPosition(p, 5, 1);
}
@Test
void testDiscardWithLineContinuation_predicateArg() {
// arrange
final char cont = '|';
final SimpleTextParser p = parser("\na,|\r\nb |c\r\n1|\r|\n2.3\rdef\n");
// act/assert
p.discardWithLineContinuation(cont, Character::isWhitespace);
assertChar('a', p.peekChar());
assertPosition(p, 2, 1);
p.discardWithLineContinuation(cont, c -> !Character.isWhitespace(c));
assertChar(' ', p.peekChar());
assertPosition(p, 3, 2);
p.discardWithLineContinuation(cont, Character::isDigit); // should not advance
assertChar(' ', p.peekChar());
assertPosition(p, 3, 2);
p.discardWithLineContinuation(cont, Character::isWhitespace);
assertChar('|', p.peekChar());
assertPosition(p, 3, 3);
p.discardWithLineContinuation(cont, c -> c != 'd');
assertChar('d', p.peekChar());
assertPosition(p, 7, 1);
p.discardWithLineContinuation(cont, c -> true);
assertChar(EOF, p.peekChar());
assertPosition(p, 8, 1);
p.discardWithLineContinuation(cont, c -> true);
assertChar(EOF, p.peekChar());
assertPosition(p, 8, 1);
}
@Test
void testDiscardWhitespace() {
// arrange
final SimpleTextParser p = parser("a\t\n\r\n b c");
// act/assert
p.discardWhitespace();
assertPosition(p, 1, 1);
assertChar('a', p.readChar());
p.discardWhitespace();
assertPosition(p, 3, 4);
assertChar('b', p.readChar());
p.discardWhitespace();
assertPosition(p, 3, 6);
assertChar('c', p.readChar());
p.discardWhitespace();
assertPosition(p, 3, 7);
assertChar(EOF, p.readChar());
}
@Test
void testDiscardLineWhitespace() {
// arrange
final SimpleTextParser p = parser("a\t\n\r\n b c");
// act/assert
p.discardLineWhitespace();
assertPosition(p, 1, 1);
assertChar('a', p.readChar());
p.discardLineWhitespace();
assertPosition(p, 1, 3);
assertChar('\n', p.peekChar());
p.discardLineWhitespace();
assertPosition(p, 1, 3);
assertChar('\n', p.readChar());
p.discardLineWhitespace();
assertPosition(p, 2, 1);
assertChar('\r', p.readChar());
p.discardLineWhitespace();
assertPosition(p, 2, 2);
assertChar('\n', p.readChar());
p.discardLineWhitespace();
assertPosition(p, 3, 4);
assertChar('b', p.readChar());
p.discardLineWhitespace();
assertPosition(p, 3, 6);
assertChar('c', p.readChar());
p.discardLineWhitespace();
assertPosition(p, 3, 7);
assertChar(EOF, p.readChar());
}
@Test
void testDiscardNewLineSequence() {
// arrange
final SimpleTextParser p = parser("a\t\n\r\n b\rc");
// act/assert
p.discardNewLineSequence();
assertPosition(p, 1, 1);
assertChar('a', p.readChar());
p.discardLineWhitespace();
p.discardNewLineSequence();
assertPosition(p, 2, 1);
assertChar('\r', p.readChar());
p.discardNewLineSequence();
assertPosition(p, 3, 1);
assertChar(' ', p.readChar());
p.discardWhitespace();
p.discardNewLineSequence();
assertPosition(p, 3, 4);
assertChar('b', p.readChar());
p.discardNewLineSequence();
assertPosition(p, 4, 1);
assertChar('c', p.readChar());
p.discardNewLineSequence();
assertPosition(p, 4, 2);
assertChar(EOF, p.readChar());
}
@Test
void testDiscardLine() {
// arrange
final SimpleTextParser p = parser("a\t\n\r\n b c");
// act/assert
p.discardLine();
assertChar('\r', p.peekChar());
assertPosition(p, 2, 1);
p.discardLine();
assertChar(' ', p.peekChar());
assertPosition(p, 3, 1);
p.discardLine();
assertPosition(p, 3, 7);
assertChar(EOF, p.peekChar());
p.discardLine();
assertPosition(p, 3, 7);
assertChar(EOF, p.peekChar());
}
@Test
void testPeek_lenArg() {
// arrange
final SimpleTextParser p = parser("abcdef\r\n\r ghi");
// act/assert
Assertions.assertEquals("", p.peek(0));
assertPosition(p, 1, 1);
Assertions.assertEquals("", p.peek(0));
assertPosition(p, 1, 1);
p.readChar();
Assertions.assertEquals("bcde", p.peek(4));
assertPosition(p, 1, 2);
Assertions.assertEquals("bcdef\r", p.peek(6));
assertPosition(p, 1, 2);
Assertions.assertEquals("bcdef\r\n\r ghi", p.peek(100));
assertPosition(p, 1, 2);
assertChar('b', p.readChar());
p.discard(c -> true);
Assertions.assertNull(p.peek(0));
Assertions.assertNull(p.peek(100));
}
@Test
void testPeek_lenArg_invalidArg() {
// arrange
final SimpleTextParser p = parser("abcdef");
p.setMaxStringLength(4);
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.peek(-1);
}, IllegalArgumentException.class, "Requested string length cannot be negative; was -1");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.peek(6);
}, IllegalArgumentException.class, "Requested string length of 6 exceeds maximum value of 4");
}
@Test
void testPeek_predicateArg() {
// arrange
final SimpleTextParser p = parser("abcdef\r\n\r ghi");
// act/assert
Assertions.assertEquals("", p.peek(c -> false));
assertPosition(p, 1, 1);
p.readChar();
Assertions.assertEquals("bcdef", p.peek(SimpleTextParser::isAlphanumeric));
assertPosition(p, 1, 2);
Assertions.assertEquals("bcdef\r\n\r ghi", p.peek(c -> true));
assertPosition(p, 1, 2);
assertChar('b', p.readChar());
p.discard(c -> true);
Assertions.assertNull(p.peek(c -> true));
Assertions.assertNull(p.peek(c -> false));
}
@Test
void testPeek_predicateArg_exceedsMaxStringLength() {
// arrange
final SimpleTextParser p = parser("\n abcdefg");
p.setMaxStringLength(4);
p.discardLine()
.discard(SimpleTextParser::isWhitespace);
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.peek(SimpleTextParser::isNotWhitespace);
}, IllegalStateException.class, "Parsing failed at line 2, column 3: string length exceeds maximum value of 4");
}
@Test
void testMatch() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
p.next(1)
.match("a")
.next(100)
.match("bcdef");
Assertions.assertFalse(p.hasMoreCharacters());
}
@Test
void testMatch_failure() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.match("empty");
}, IllegalStateException.class, "No token has been read from the character stream");
p.next(1);
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.match("b");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [b] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.match("A");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [A] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.match(null);
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [null] but found [a]");
}
@Test
void testMatch_ignoreCase() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
p.next(1)
.matchIgnoreCase("A")
.next(100)
.matchIgnoreCase("BcdEF");
Assertions.assertFalse(p.hasMoreCharacters());
}
@Test
void testMatchIgnoreCase_failure() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.matchIgnoreCase("empty");
}, IllegalStateException.class, "No token has been read from the character stream");
p.next(1);
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.matchIgnoreCase("b");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [b] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.match(null);
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [null] but found [a]");
}
@Test
void testTryMatch() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
p.next(3);
Assertions.assertTrue(p.tryMatch("abc"));
Assertions.assertFalse(p.tryMatch("ab"));
Assertions.assertFalse(p.tryMatch(""));
Assertions.assertFalse(p.tryMatch(null));
Assertions.assertFalse(p.tryMatch("ABC"));
Assertions.assertFalse(p.tryMatch("aBc"));
p.next(1);
Assertions.assertTrue(p.tryMatch(null));
}
@Test
void testTryMatch_noToken() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.tryMatch("empty");
}, IllegalStateException.class, "No token has been read from the character stream");
}
@Test
void testTryMatchIgnoreCase() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
p.next(3);
Assertions.assertTrue(p.tryMatchIgnoreCase("abc"));
Assertions.assertTrue(p.tryMatchIgnoreCase("ABC"));
Assertions.assertTrue(p.tryMatchIgnoreCase("aBc"));
Assertions.assertFalse(p.tryMatch("ab"));
Assertions.assertFalse(p.tryMatch(""));
Assertions.assertFalse(p.tryMatch(null));
p.next(1);
Assertions.assertTrue(p.tryMatch(null));
}
@Test
void testTryMatchIgnoreCase_noToken() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.tryMatchIgnoreCase("empty");
}, IllegalStateException.class, "No token has been read from the character stream");
}
@Test
void testChoose() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
p.next(1);
Assertions.assertEquals(0, p.choose("a"));
Assertions.assertEquals(0, p.choose("a", "b", "c"));
Assertions.assertEquals(2, p.choose("c", "b", "a"));
p.next(1);
Assertions.assertEquals(0, p.choose("b"));
Assertions.assertEquals(1, p.choose("a", "b", "c"));
Assertions.assertEquals(1, p.choose("c", "b", "a"));
}
@Test
void testChoose_failure() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.choose("X");
}, IllegalStateException.class, "No token has been read from the character stream");
p.next(1);
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.choose("X");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.choose("X", "Y", "Z");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X, Y, Z] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.choose("A");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [A] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(p::choose,
IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [] but found [a]");
}
@Test
void testChooseIgnoreCase() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
p.next(1);
Assertions.assertEquals(0, p.chooseIgnoreCase("A"));
Assertions.assertEquals(0, p.chooseIgnoreCase("A", "b", "C"));
Assertions.assertEquals(2, p.chooseIgnoreCase("C", "b", "A"));
p.next(1);
Assertions.assertEquals(0, p.chooseIgnoreCase("b"));
Assertions.assertEquals(1, p.chooseIgnoreCase("A", "b", "C"));
Assertions.assertEquals(1, p.chooseIgnoreCase("C", "b", "A"));
}
@Test
void testChooseIgnoreCase_failure() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.chooseIgnoreCase("X");
}, IllegalStateException.class, "No token has been read from the character stream");
p.next(1);
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.chooseIgnoreCase("X");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.chooseIgnoreCase("X", "Y", "Z");
}, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X, Y, Z] but found [a]");
GeometryTestUtils.assertThrowsWithMessage(p::chooseIgnoreCase,
IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [] but found [a]");
}
@Test
void testTryChoose() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
p.next(1);
Assertions.assertEquals(0, p.tryChoose("a"));
Assertions.assertEquals(0, p.tryChoose("a", "b", "c"));
Assertions.assertEquals(2, p.tryChoose("c", "b", "a"));
p.next(1);
Assertions.assertEquals(0, p.tryChoose("b"));
Assertions.assertEquals(1, p.tryChoose("a", "b", "c"));
Assertions.assertEquals(1, p.tryChoose("c", "b", "a"));
Assertions.assertEquals(-1, p.tryChoose("A", "B", "C"));
Assertions.assertEquals(-1, p.tryChoose());
Assertions.assertEquals(-1, p.tryChoose((String) null));
}
@Test
void testTryChoose_noToken() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.tryChoose("X");
}, IllegalStateException.class, "No token has been read from the character stream");
}
@Test
void testTryChooseIgnoreCase() {
// arrange
final SimpleTextParser p = parser("abc");
// act/assert
p.next(1);
Assertions.assertEquals(0, p.tryChooseIgnoreCase("a"));
Assertions.assertEquals(0, p.tryChooseIgnoreCase("A", "B", "C"));
Assertions.assertEquals(2, p.tryChooseIgnoreCase("C", "b", "A"));
p.next(1);
Assertions.assertEquals(0, p.tryChooseIgnoreCase("B"));
Assertions.assertEquals(1, p.tryChooseIgnoreCase("a", "B", "c"));
Assertions.assertEquals(1, p.tryChooseIgnoreCase("c", "b", "a"));
Assertions.assertEquals(-1, p.tryChooseIgnoreCase("X", "Y", "Z"));
Assertions.assertEquals(-1, p.tryChooseIgnoreCase());
Assertions.assertEquals(-1, p.tryChooseIgnoreCase((String) null));
}
@Test
void testTryChooseIgnoreCase_noToken() {
// arrange
final SimpleTextParser p = parser("abcdef");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
p.tryChooseIgnoreCase("X");
}, IllegalStateException.class, "No token has been read from the character stream");
}
@Test
void testUnexpectedToken() {
// arrange
final SimpleTextParser p = parser("abc\ndef");
// act/assert
Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found no current token",
p.unexpectedToken("test").getMessage());
p.nextAlphanumeric();
Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found [abc]",
p.unexpectedToken("test").getMessage());
p.nextAlphanumeric();
Assertions.assertEquals("Parsing failed at line 1, column 4: expected test but found end of line",
p.unexpectedToken("test").getMessage());
p.discardLine();
p.next(SimpleTextParser::isWhitespace);
Assertions.assertEquals("Parsing failed at line 2, column 1: expected test but found empty token followed by [d]",
p.unexpectedToken("test").getMessage());
p.next(3).next(10);
Assertions.assertEquals("Parsing failed at line 2, column 4: expected test but found end of content",
p.unexpectedToken("test").getMessage());
}
@Test
void testUnexpectedToken_causeArg() {
// arrange
final SimpleTextParser p = parser("abc");
final Exception cause = new Exception("test");
// act/assert
p.nextLine();
final IllegalStateException exc = p.unexpectedToken("test", cause);
Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found [abc]",
exc.getMessage());
Assertions.assertSame(cause, exc.getCause());
}
@Test
void testUnexpectedToken_ioError() {
// arrange
final FailBuffer b = new FailBuffer(new StringReader("abc"));
final SimpleTextParser p = new SimpleTextParser(b);
// act/assert
b.setFail(false);
p.next(SimpleTextParser::isDecimalPart);
b.setFail(true);
Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found empty token",
p.unexpectedToken("test").getMessage());
b.setFail(false);
p.nextAlphanumeric();
b.setFail(true);
Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found [abc]",
p.unexpectedToken("test").getMessage());
b.setFail(false);
p.nextAlphanumeric();
b.setFail(true);
Assertions.assertEquals("Parsing failed at line 1, column 4: expected test but found no current token",
p.unexpectedToken("test").getMessage());
}
@Test
void testTokenError() {
// arrange
final SimpleTextParser p = parser("a\nbc");
p.nextLine();
p.next(1);
p.readChar();
// act/assert
final IllegalStateException exc = p.tokenError("test message");
Assertions.assertEquals("Parsing failed at line 2, column 1: test message", exc.getMessage());
Assertions.assertNull(exc.getCause());
}
@Test
void testTokenError_noTokenSet() {
// arrange
final SimpleTextParser p = parser("ab\nc");
p.readChar();
// act/assert
final IllegalStateException exc = p.tokenError("test message");
Assertions.assertEquals("Parsing failed at line 1, column 2: test message", exc.getMessage());
Assertions.assertNull(exc.getCause());
}
@Test
void testTokenError_withCause() {
// arrange
SimpleTextParser p = parser("a\nbc");
p.nextLine();
p.next(1);
p.readChar();
final Exception cause = new Exception("test");
// act/assert
final IllegalStateException exc = p.tokenError("test message", cause);
Assertions.assertEquals("Parsing failed at line 2, column 1: test message", exc.getMessage());
Assertions.assertSame(cause, exc.getCause());
}
@Test
void testParseError_currentLineCol() {
// arrange
final SimpleTextParser p = parser("a\nbc");
p.discard(ch -> ch != 'b');
// act
final IllegalStateException exc = p.parseError("test message");
Assertions.assertEquals("Parsing failed at line 2, column 1: test message", exc.getMessage());
Assertions.assertNull(exc.getCause());
}
@Test
void testParseError_currentLineCol_withCause() {
// arrange
final SimpleTextParser p = parser("abc");
p.readChar();
final Exception cause = new Exception("test");
// act
final IllegalStateException exc = p.parseError("test message", cause);
Assertions.assertEquals("Parsing failed at line 1, column 2: test message", exc.getMessage());
Assertions.assertSame(cause, exc.getCause());
}
@Test
void testParseError_givenLineCol() {
// arrange
final SimpleTextParser p = parser("abc");
// act
final IllegalStateException exc = p.parseError(5, 6, "test message");
Assertions.assertEquals("Parsing failed at line 5, column 6: test message", exc.getMessage());
Assertions.assertNull(exc.getCause());
}
@Test
void testParseError_givenLineCol_withCause() {
// arrange
final SimpleTextParser p = parser("abc");
final Exception cause = new Exception("test");
// act
final IllegalStateException exc = p.parseError(5, 6, "test message", cause);
Assertions.assertEquals("Parsing failed at line 5, column 6: test message", exc.getMessage());
Assertions.assertSame(cause, exc.getCause());
}
@Test
void testCharacterPredicates() {
// act/assert
assertMatchesAll(SimpleTextParser::isWhitespace, " \t\n\r");
assertDoesNotMatchAny(SimpleTextParser::isWhitespace, "abcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
assertMatchesAll(SimpleTextParser::isNotWhitespace, "abcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
assertDoesNotMatchAny(SimpleTextParser::isNotWhitespace, " \t\n\r");
assertMatchesAll(SimpleTextParser::isLineWhitespace, " \t");
assertDoesNotMatchAny(SimpleTextParser::isLineWhitespace, "\n\rabcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
assertMatchesAll(SimpleTextParser::isNewLinePart, "\n\r");
assertDoesNotMatchAny(SimpleTextParser::isNewLinePart, " \tabcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
assertMatchesAll(SimpleTextParser::isNotNewLinePart, " \tabcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
assertDoesNotMatchAny(SimpleTextParser::isNotNewLinePart, "\n\r");
assertMatchesAll(SimpleTextParser::isAlphanumeric, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
assertDoesNotMatchAny(SimpleTextParser::isAlphanumeric, " \t\n\r./?:;'\\\"[]{}`~!@#$%^&*()_+-=");
assertMatchesAll(SimpleTextParser::isNotAlphanumeric, " \t\n\r./?:;'\\\"[]{}`~!@#$%^&*()_+-=");
assertDoesNotMatchAny(SimpleTextParser::isNotAlphanumeric, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
assertMatchesAll(SimpleTextParser::isIntegerPart, "0123456789+-");
assertDoesNotMatchAny(SimpleTextParser::isIntegerPart, " \t\n\r./?:;'\\\"[]{}`~!@#$%^&*()_=abcdeABCDE");
assertMatchesAll(SimpleTextParser::isDecimalPart, "0123456789+-.eE");
assertDoesNotMatchAny(SimpleTextParser::isDecimalPart, " \t\n\r/?:;'\\\"[]{}`~!@#$%^&*()_=abcdABCD");
}
private static SimpleTextParser parser(final String content) {
final StringReader reader = new StringReader(content);
return new SimpleTextParser(reader);
}
private static void assertCharacterSequence(final SimpleTextParser parser, final String expected) {
char expectedChar;
String msg;
for (int i = 0; i < expected.length(); ++i) {
expectedChar = expected.charAt(i);
msg = "Failed at index " + i + ":";
Assertions.assertEquals(expectedChar, parser.peekChar(), msg);
Assertions.assertEquals(expectedChar, parser.peekChar(), msg);
Assertions.assertTrue(parser.hasMoreCharacters());
Assertions.assertEquals(expectedChar, parser.readChar(), msg);
}
Assertions.assertFalse(parser.hasMoreCharacters());
Assertions.assertEquals(-1, parser.peekChar());
Assertions.assertEquals(-1, parser.peekChar());
Assertions.assertEquals(-1, parser.readChar());
}
private static void assertChar(final int expected, final int actual) {
final String expectedStr = describeChar(expected);
final String actualStr = describeChar(actual);
Assertions.assertEquals(expected, actual, "Expected [" + expectedStr + "] but was [" + actualStr + "];");
}
private static void assertMatchesAll(final IntPredicate pred, final String chars) {
for (char ch : chars.toCharArray()) {
final String msg = "Expected predicate to match [" + describeChar(ch) + "]";
Assertions.assertTrue(pred.test(ch), msg);
}
}
private static void assertDoesNotMatchAny(final IntPredicate pred, final String chars) {
for (char ch : chars.toCharArray()) {
final String msg = "Expected predicate to not match [" + describeChar(ch) + "]";
Assertions.assertFalse(pred.test(ch), msg);
}
}
private static String describeChar(final int ch) {
switch (ch) {
case '\n':
return "\\n";
case '\r':
return "\\r";
case '\t':
return "\\t";
case EOF:
return "EOF";
default:
return String.valueOf((char) ch);
}
}
private static void assertPosition(final SimpleTextParser parser, final int line, final int col) {
Assertions.assertEquals(line, parser.getLineNumber(), "Unexpected line number");
Assertions.assertEquals(col, parser.getColumnNumber(), "Unexpected column number");
}
private static void assertToken(final SimpleTextParser parser, final String token, final int line, final int col) {
Assertions.assertEquals(token, parser.getCurrentToken(), "Unexpected token");
Assertions.assertEquals(line, parser.getCurrentTokenLineNumber(), "Unexpected token line number");
Assertions.assertEquals(col, parser.getCurrentTokenColumnNumber(), "Unexpected token column number");
}
private static final class FailBuffer extends CharReadBuffer {
private boolean fail;
FailBuffer(final Reader in) {
super(in);
}
public void setFail(final boolean fail) {
this.fail = fail;
}
@Override
public boolean hasMoreCharacters() {
checkFail();
return super.hasMoreCharacters();
}
private void checkFail() {
if (fail) {
throw new IllegalStateException("test failure");
}
}
}
}