View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.io.core.internal;
    18
    19  import java.io.Reader;
    20  import java.io.StringReader;
    21  import java.util.function.IntPredicate;
    22
    23  import org.apache.commons.geometry.core.GeometryTestUtils;
    24  import org.junit.jupiter.api.Assertions;
    25  import org.junit.jupiter.api.Test;
    26
    27  class SimpleTextParserTest {
    28
    29      private static final double EPS = 1e-20;
    30
    31      private static final int EOF = -1;
    32
    33      @Test
    34      void testMaxStringLength_defaultValue() {
    35          // arrange
    36          final SimpleTextParser p = parser("abc");
    37
    38          // act/assert
    39          Assertions.assertEquals(1024, p.getMaxStringLength());
    40      }
    41
    42      @Test
    43      void testMaxStringLength_illegalArg() {
    44          // arrange
    45          final SimpleTextParser p = parser("abc");
    46
    47          // act/assert
    48          GeometryTestUtils.assertThrowsWithMessage(() -> {
    49              p.setMaxStringLength(-1);
    50          }, IllegalArgumentException.class, "Maximum string length cannot be less than zero; was -1");
    51      }
    52
    53      @Test
    54      void testCharacterSequence() {
    55          // act/assert
    56          assertCharacterSequence(parser(""), "");
    57          assertCharacterSequence(parser("abc def"), "abc def");
    58      }
    59
    60      @Test
    61      void testCharacterPosition() {
    62          // arrange
    63          final SimpleTextParser p = parser(
    64                  "a b\n" +
    65                  "\r\n" +
    66                  "d \r" +
    67                  "e");
    68
    69          // act/assert
    70          assertPosition(p, 1, 1);
    71          assertChar('a', p.readChar());
    72
    73          assertPosition(p, 1, 2);
    74          assertChar(' ', p.readChar());
    75
    76          assertPosition(p, 1, 3);
    77          assertChar('b', p.readChar());
    78
    79          assertPosition(p, 1, 4);
    80          assertChar('\n', p.readChar());
    81
    82          assertPosition(p, 2, 1);
    83          assertChar('\r', p.readChar());
    84
    85          assertPosition(p, 2, 2);
    86          assertChar('\n', p.readChar());
    87
    88          assertPosition(p, 3, 1);
    89          assertChar('d', p.readChar());
    90
    91          assertPosition(p, 3, 2);
    92          assertChar(' ', p.readChar());
    93
    94          assertPosition(p, 3, 3);
    95          assertChar('\r', p.readChar());
    96
    97          assertPosition(p, 4, 1);
    98          assertChar('e', p.readChar());
    99
    100         assertPosition(p, 4, 2);
    101         assertChar(EOF, p.readChar());
    102     }
    103
    104     @Test
    105     void testCharacterPosition_givenPosition() {
    106         // arrange
    107         final SimpleTextParser p = parser("abc\rdef");
    108
    109         // act/assert
    110         assertPosition(p, 1, 1);
    111
    112         p.setLineNumber(10);
    113         p.setColumnNumber(3);
    114
    115         assertPosition(p, 10, 3);
    116
    117         p.discard(4);
    118
    119         assertPosition(p, 11, 1);
    120
    121         p.discard(3);
    122
    123         assertPosition(p, 11, 4);
    124     }
    125
    126     @Test
    127     void testHasMoreCharacters() {
    128         // arrange
    129         final SimpleTextParser empty = parser("");
    130         final SimpleTextParser nonEmpty = parser("a");
    131
    132         // act/assert
    133         Assertions.assertFalse(empty.hasMoreCharacters());
    134
    135         Assertions.assertTrue(nonEmpty.hasMoreCharacters());
    136         assertChar('a', nonEmpty.readChar());
    137         Assertions.assertFalse(nonEmpty.hasMoreCharacters());
    138     }
    139
    140     @Test
    141     void testHasMoreCharactersOnLine() {
    142         // arrange
    143         final SimpleTextParser empty = parser("");
    144         final SimpleTextParser singleLine = parser("a");
    145         final SimpleTextParser multiLine = parser("a\r\nb\rc\n\n");
    146
    147         // act/assert
    148         Assertions.assertFalse(empty.hasMoreCharactersOnLine());
    149
    150         Assertions.assertTrue(singleLine.hasMoreCharactersOnLine());
    151         assertChar('a', singleLine.readChar());
    152         Assertions.assertFalse(singleLine.hasMoreCharactersOnLine());
    153
    154         Assertions.assertTrue(multiLine.hasMoreCharactersOnLine());
    155         assertChar('a', multiLine.readChar());
    156
    157         Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
    158         assertChar('\r', multiLine.readChar());
    159
    160         Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
    161         assertChar('\n', multiLine.readChar());
    162
    163         Assertions.assertTrue(multiLine.hasMoreCharactersOnLine());
    164         assertChar('b', multiLine.readChar());
    165
    166         Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
    167         assertChar('\r', multiLine.readChar());
    168
    169         Assertions.assertTrue(multiLine.hasMoreCharactersOnLine());
    170         assertChar('c', multiLine.readChar());
    171
    172         Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
    173         assertChar('\n', multiLine.readChar());
    174
    175         Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
    176         assertChar('\n', multiLine.readChar());
    177
    178         Assertions.assertFalse(multiLine.hasMoreCharactersOnLine());
    179         assertChar(EOF, multiLine.readChar());
    180     }
    181
    182     @Test
    183     void testBasicTokenMethods() {
    184         // arrange
    185         final SimpleTextParser p = parser("abcdef\r\n\r ghi");
    186
    187         // act/assert
    188         assertToken(p, null, -1, -1);
    189         Assertions.assertFalse(p.hasNonEmptyToken());
    190
    191         assertToken(p.next(1), "a", 1, 1);
    192         Assertions.assertTrue(p.hasNonEmptyToken());
    193
    194         assertToken(p.next(3), "bcd", 1, 2);
    195         Assertions.assertTrue(p.hasNonEmptyToken());
    196
    197         assertToken(p.next(5), "ef\r\n\r", 1, 5);
    198         Assertions.assertTrue(p.hasNonEmptyToken());
    199
    200         assertToken(p.next(0), "", 3, 1);
    201         Assertions.assertFalse(p.hasNonEmptyToken());
    202
    203         assertToken(p.next(1), " ", 3, 1);
    204         Assertions.assertTrue(p.hasNonEmptyToken());
    205
    206         assertToken(p.next(3), "ghi", 3, 2);
    207         Assertions.assertTrue(p.hasNonEmptyToken());
    208
    209         assertToken(p.next(1), null, 3, 5);
    210         Assertions.assertFalse(p.hasNonEmptyToken());
    211     }
    212
    213     @Test
    214     void testGetCurrentTokenAsDouble() {
    215         // arrange
    216         final SimpleTextParser p = parser("1e-4\n+5\n-4.001");
    217
    218         // act/assert
    219         p.nextLine();
    220         Assertions.assertEquals(1e-4, p.getCurrentTokenAsDouble(), EPS);
    221
    222         p.nextLine();
    223         Assertions.assertEquals(5.0, p.getCurrentTokenAsDouble(), EPS);
    224
    225         p.nextLine();
    226         Assertions.assertEquals(-4.001, p.getCurrentTokenAsDouble(), EPS);
    227     }
    228
    229     @Test
    230     void testGetCurrentTokenAsDouble_failures() {
    231         // arrange
    232         final SimpleTextParser p = parser("abc\n1.1.1a");
    233
    234         // act/assert
    235         GeometryTestUtils.assertThrowsWithMessage(() -> {
    236             p.getCurrentTokenAsDouble();
    237         }, IllegalStateException.class, "No token has been read from the character stream");
    238
    239         p.next(SimpleTextParser::isNotNewLinePart);
    240         GeometryTestUtils.assertThrowsWithMessage(() -> {
    241             p.getCurrentTokenAsDouble();
    242         }, IllegalStateException.class,
    243                 "Parsing failed at line 1, column 1: expected double but found [abc]");
    244
    245         p.nextAlphanumeric();
    246         GeometryTestUtils.assertThrowsWithMessage(() -> {
    247             p.getCurrentTokenAsDouble();
    248         }, IllegalStateException.class,
    249                 "Parsing failed at line 1, column 4: expected double but found end of line");
    250
    251         p.discardLine()
    252             .next(c -> c != 'a');
    253         GeometryTestUtils.assertThrowsWithMessage(() -> {
    254             p.getCurrentTokenAsDouble();
    255         }, IllegalStateException.class,
    256                 "Parsing failed at line 2, column 1: expected double but found [1.1.1]");
    257
    258         p.next(Character::isDigit);
    259         GeometryTestUtils.assertThrowsWithMessage(() -> {
    260             p.getCurrentTokenAsDouble();
    261         }, IllegalStateException.class,
    262                 "Parsing failed at line 2, column 6: expected double but found empty token followed by [a]");
    263
    264         p.nextLine();
    265         GeometryTestUtils.assertThrowsWithMessage(() -> {
    266             p.getCurrentTokenAsDouble();
    267         }, IllegalStateException.class,
    268                 "Parsing failed at line 2, column 6: expected double but found [a]");
    269
    270         p.nextLine();
    271         GeometryTestUtils.assertThrowsWithMessage(() -> {
    272             p.getCurrentTokenAsDouble();
    273         }, IllegalStateException.class,
    274                 "Parsing failed at line 2, column 7: expected double but found end of content");
    275     }
    276
    277     @Test
    278     void testGetCurrentTokenAsDouble_includedNumberFormatExceptionOnFailure() {
    279         // arrange
    280         final SimpleTextParser p = parser("abc");
    281         p.nextLine();
    282
    283         // act/assert
    284         final Throwable exc = Assertions.assertThrows(IllegalStateException.class, () -> p.getCurrentTokenAsDouble());
    285         Assertions.assertEquals(NumberFormatException.class, exc.getCause().getClass());
    286     }
    287
    288     @Test
    289     void testGetCurrentTokenAsInt() {
    290         // arrange
    291         final SimpleTextParser p = parser("0\n+5\n-401");
    292
    293         // act/assert
    294         p.nextLine();
    295         Assertions.assertEquals(0, p.getCurrentTokenAsInt());
    296
    297         p.nextLine();
    298         Assertions.assertEquals(5, p.getCurrentTokenAsInt());
    299
    300         p.nextLine();
    301         Assertions.assertEquals(-401, p.getCurrentTokenAsInt());
    302     }
    303
    304     @Test
    305     void testGetCurrentTokenAsInt_failures() {
    306         // arrange
    307         final SimpleTextParser p = parser("abc\n1.1.1a");
    308
    309         // act/assert
    310         GeometryTestUtils.assertThrowsWithMessage(() -> {
    311             p.getCurrentTokenAsInt();
    312         }, IllegalStateException.class, "No token has been read from the character stream");
    313
    314         p.next(SimpleTextParser::isNotNewLinePart);
    315         GeometryTestUtils.assertThrowsWithMessage(() -> {
    316             p.getCurrentTokenAsInt();
    317         }, IllegalStateException.class,
    318                 "Parsing failed at line 1, column 1: expected integer but found [abc]");
    319
    320         p.nextAlphanumeric();
    321         GeometryTestUtils.assertThrowsWithMessage(() -> {
    322             p.getCurrentTokenAsInt();
    323         }, IllegalStateException.class,
    324                 "Parsing failed at line 1, column 4: expected integer but found end of line");
    325
    326         p.discardLine()
    327             .next(c -> c != 'a');
    328         GeometryTestUtils.assertThrowsWithMessage(() -> {
    329             p.getCurrentTokenAsInt();
    330         }, IllegalStateException.class,
    331                 "Parsing failed at line 2, column 1: expected integer but found [1.1.1]");
    332
    333         p.next(Character::isDigit);
    334         GeometryTestUtils.assertThrowsWithMessage(() -> {
    335             p.getCurrentTokenAsInt();
    336         }, IllegalStateException.class,
    337                 "Parsing failed at line 2, column 6: expected integer but found empty token followed by [a]");
    338
    339         p.nextLine();
    340         GeometryTestUtils.assertThrowsWithMessage(() -> {
    341             p.getCurrentTokenAsInt();
    342         }, IllegalStateException.class,
    343                 "Parsing failed at line 2, column 6: expected integer but found [a]");
    344
    345         p.nextLine();
    346         GeometryTestUtils.assertThrowsWithMessage(() -> {
    347             p.getCurrentTokenAsInt();
    348         }, IllegalStateException.class,
    349                 "Parsing failed at line 2, column 7: expected integer but found end of content");
    350     }
    351
    352     @Test
    353     void testGetCurrentTokenAsInt_includedNumberFormatExceptionOnFailure() {
    354         // arrange
    355         final SimpleTextParser p = parser("abc");
    356         p.nextLine();
    357
    358         // act/assert
    359         final Throwable exc = Assertions.assertThrows(IllegalStateException.class, () -> p.getCurrentTokenAsInt());
    360         Assertions.assertEquals(NumberFormatException.class, exc.getCause().getClass());
    361     }
    362
    363     @Test
    364     void testNext_lenArg() {
    365         // arrange
    366         final SimpleTextParser p = parser("abcdef\r\n\r ghi");
    367
    368         // act/assert
    369         assertToken(p.next(0), "", 1, 1);
    370         assertToken(p.next(4), "abcd", 1, 1);
    371         assertToken(p.next(6), "ef\r\n\r ", 1, 5);
    372         assertToken(p.next(100), "ghi", 3, 2);
    373
    374         assertToken(p.next(0), null, 3, 5);
    375         assertToken(p.next(100), null, 3, 5);
    376     }
    377
    378     @Test
    379     void testNextWithLineContinuation_lenArg() {
    380         // arrange
    381         final char cont = '\\';
    382         final SimpleTextParser p = parser("a\\bcdef\\\r\n\r ghi\\\n\\\n\\\rj");
    383
    384         // act/assert
    385         assertToken(p.nextWithLineContinuation(cont, 0), "", 1, 1);
    386         assertToken(p.nextWithLineContinuation(cont, 5), "a\\bcd", 1, 1);
    387         assertToken(p.nextWithLineContinuation(cont, 3), "ef\r", 1, 6);
    388         assertToken(p.nextWithLineContinuation(cont, 100), " ghij", 3, 1);
    389
    390         assertToken(p.nextWithLineContinuation(cont, 0), null, 6, 2);
    391         assertToken(p.nextWithLineContinuation(cont, 100), null, 6, 2);
    392     }
    393
    394     @Test
    395     void testNext_lenArg_invalidArg() {
    396         // arrange
    397         final SimpleTextParser p = parser("abc");
    398         p.setMaxStringLength(2);
    399
    400         // act/assert
    401         GeometryTestUtils.assertThrowsWithMessage(() -> {
    402             p.next(-1);
    403         }, IllegalArgumentException.class, "Requested string length cannot be negative; was -1");
    404
    405         GeometryTestUtils.assertThrowsWithMessage(() -> {
    406             p.next(3);
    407         }, IllegalArgumentException.class, "Requested string length of 3 exceeds maximum value of 2");
    408     }
    409
    410     @Test
    411     void testNext_predicateArg() {
    412         // arrange
    413         final SimpleTextParser p = parser("a\n 012\r\ndef");
    414
    415         // act/assert
    416         assertToken(p.next(c -> false), "", 1, 1);
    417
    418         assertToken(p.next(Character::isAlphabetic), "a", 1, 1);
    419         assertToken(p.next(Character::isAlphabetic), "", 1, 2);
    420
    421         assertToken(p.next(Character::isWhitespace), "\n ", 1, 2);
    422         assertToken(p.next(Character::isWhitespace), "", 2, 2);
    423
    424         assertToken(p.next(Character::isDigit), "012", 2, 2);
    425         assertToken(p.next(Character::isDigit), "", 2, 5);
    426
    427         assertToken(p.next(Character::isWhitespace), "\r\n", 2, 5);
    428         assertToken(p.next(Character::isWhitespace), "", 3, 1);
    429
    430         assertToken(p.next(c -> true), "def", 3, 1);
    431         assertToken(p.next(c -> true), null, 3, 4);
    432     }
    433
    434     @Test
    435     void testNext_predicateArg_exceedsMaxStringLength() {
    436         // arrange
    437         final SimpleTextParser p = parser("abcdef");
    438         p.setMaxStringLength(4);
    439
    440         // act/assert
    441         GeometryTestUtils.assertThrowsWithMessage(() -> {
    442             p.next(c -> !Character.isWhitespace(c));
    443         }, IllegalStateException.class, "Parsing failed at line 1, column 1: string length exceeds maximum value of 4");
    444     }
    445
    446     @Test
    447     void testNextWithLineContinuation_predicateArg() {
    448         // arrange
    449         final char cont = '|';
    450         final SimpleTextParser p = parser("|\na\n 0|\r\n|\r12\r\nd|ef");
    451
    452         // act/assert
    453         assertToken(p.nextWithLineContinuation(cont, c -> false), "", 1, 1);
    454
    455         assertToken(p.nextWithLineContinuation(cont, Character::isAlphabetic), "a", 2, 1);
    456         assertToken(p.nextWithLineContinuation(cont, Character::isAlphabetic), "", 2, 2);
    457
    458         assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "\n ", 2, 2);
    459         assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "", 3, 2);
    460
    461         assertToken(p.nextWithLineContinuation(cont, Character::isDigit), "012", 3, 2);
    462         assertToken(p.nextWithLineContinuation(cont, Character::isDigit), "", 5, 3);
    463
    464         assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "\r\n", 5, 3);
    465         assertToken(p.nextWithLineContinuation(cont, Character::isWhitespace), "", 6, 1);
    466
    467         assertToken(p.nextWithLineContinuation(cont, c -> true), "d|ef", 6, 1);
    468         assertToken(p.nextWithLineContinuation(cont, c -> true), null, 6, 5);
    469     }
    470
    471     @Test
    472     void testNextLine() {
    473         // arrange
    474         final SimpleTextParser p = parser("a\n 012\r\ndef\n\nx");
    475
    476         // act/assert
    477         assertToken(p.nextLine(), "a", 1, 1);
    478
    479         assertToken(p.nextLine(), " 012", 2, 1);
    480
    481         p.readChar();
    482         assertToken(p.nextLine(), "ef", 3, 2);
    483
    484         assertToken(p.nextLine(), "", 4, 1);
    485
    486         assertToken(p.nextLine(), "x", 5, 1);
    487         assertToken(p.nextLine(), null, 5, 2);
    488     }
    489
    490     @Test
    491     void testNextAlphanumeric() {
    492         // arrange
    493         final SimpleTextParser p = parser("a10Fd;X23456789-0\ny");
    494
    495         // act/assert
    496         assertToken(p.nextAlphanumeric(), "a10Fd", 1, 1);
    497
    498         assertChar(';', p.readChar());
    499         assertToken(p.nextAlphanumeric(), "X23456789", 1, 7);
    500
    501         assertChar('-', p.readChar());
    502         assertToken(p.nextAlphanumeric(), "0", 1, 17);
    503
    504         assertToken(p.nextAlphanumeric(), "", 1, 18);
    505
    506         assertChar('\n', p.readChar());
    507         assertToken(p.nextAlphanumeric(), "y", 2, 1);
    508
    509         assertToken(p.nextAlphanumeric(), null, 2, 2);
    510     }
    511
    512     @Test
    513     void testDiscard_lenArg() {
    514         // arrange
    515         final SimpleTextParser p = parser("\na,b c\r\n12.3\rdef\n");
    516
    517         // act/assert
    518         p.discard(0);
    519         assertChar('\n', p.peekChar());
    520         assertPosition(p, 1, 1);
    521
    522         p.discard(1);
    523         assertChar('a', p.peekChar());
    524         assertPosition(p, 2, 1);
    525
    526         p.discard(8);
    527         assertChar('2', p.peekChar());
    528         assertPosition(p, 3, 2);
    529
    530         p.discard(100);
    531         assertChar(EOF, p.peekChar());
    532         assertPosition(p, 5, 1);
    533
    534         p.discard(0);
    535         assertChar(EOF, p.peekChar());
    536         assertPosition(p, 5, 1);
    537
    538         p.discard(100);
    539         assertChar(EOF, p.peekChar());
    540         assertPosition(p, 5, 1);
    541     }
    542
    543     @Test
    544     void testDiscardWithLineContinuation_lenArg() {
    545         // arrange
    546         final char cont = '|';
    547         final SimpleTextParser p = parser("\n|a|\r\n,b|\n|\r c\r\n12.3\rdef\n");
    548
    549         // act/assert
    550         p.discardWithLineContinuation(cont, 0);
    551         assertChar('\n', p.peekChar());
    552         assertPosition(p, 1, 1);
    553
    554         p.discardWithLineContinuation(cont, 1);
    555         assertChar('|', p.peekChar());
    556         assertPosition(p, 2, 1);
    557
    558         p.discardWithLineContinuation(cont, 8);
    559         assertChar('1', p.peekChar());
    560         assertPosition(p, 6, 1);
    561
    562         p.discardWithLineContinuation(cont, 100);
    563         assertChar(EOF, p.peekChar());
    564         assertPosition(p, 8, 1);
    565
    566         p.discardWithLineContinuation(cont, 0);
    567         assertChar(EOF, p.peekChar());
    568         assertPosition(p, 8, 1);
    569
    570         p.discardWithLineContinuation(cont, 100);
    571         assertChar(EOF, p.peekChar());
    572         assertPosition(p, 8, 1);
    573     }
    574
    575     @Test
    576     void testDiscard_predicateArg() {
    577         // arrange
    578         final SimpleTextParser p = parser("\na,b c\r\n12.3\rdef\n");
    579
    580         // act/assert
    581         p.discard(c -> Character.isWhitespace(c));
    582         assertChar('a', p.peekChar());
    583         assertPosition(p, 2, 1);
    584
    585         p.discard(c -> !Character.isWhitespace(c));
    586         assertChar(' ', p.peekChar());
    587         assertPosition(p, 2, 4);
    588
    589         p.discard(c -> Character.isDigit(c)); // should not advance
    590         assertChar(' ', p.peekChar());
    591         assertPosition(p, 2, 4);
    592
    593         p.discard(c -> Character.isWhitespace(c));
    594         assertChar('c', p.peekChar());
    595         assertPosition(p, 2, 5);
    596
    597         p.discard(c -> c != 'd');
    598         assertChar('d', p.peekChar());
    599         assertPosition(p, 4, 1);
    600
    601         p.discard(c -> true);
    602         assertChar(EOF, p.peekChar());
    603         assertPosition(p, 5, 1);
    604
    605         p.discard(c -> true);
    606         assertChar(EOF, p.peekChar());
    607         assertPosition(p, 5, 1);
    608     }
    609
    610     @Test
    611     void testDiscardWithLineContinuation_predicateArg() {
    612         // arrange
    613         final char cont = '|';
    614         final SimpleTextParser p = parser("\na,|\r\nb |c\r\n1|\r|\n2.3\rdef\n");
    615
    616         // act/assert
    617         p.discardWithLineContinuation(cont, c -> Character.isWhitespace(c));
    618         assertChar('a', p.peekChar());
    619         assertPosition(p, 2, 1);
    620
    621         p.discardWithLineContinuation(cont, c -> !Character.isWhitespace(c));
    622         assertChar(' ', p.peekChar());
    623         assertPosition(p, 3, 2);
    624
    625         p.discardWithLineContinuation(cont, c -> Character.isDigit(c)); // should not advance
    626         assertChar(' ', p.peekChar());
    627         assertPosition(p, 3, 2);
    628
    629         p.discardWithLineContinuation(cont, c -> Character.isWhitespace(c));
    630         assertChar('|', p.peekChar());
    631         assertPosition(p, 3, 3);
    632
    633         p.discardWithLineContinuation(cont, c -> c != 'd');
    634         assertChar('d', p.peekChar());
    635         assertPosition(p, 7, 1);
    636
    637         p.discardWithLineContinuation(cont, c -> true);
    638         assertChar(EOF, p.peekChar());
    639         assertPosition(p, 8, 1);
    640
    641         p.discardWithLineContinuation(cont, c -> true);
    642         assertChar(EOF, p.peekChar());
    643         assertPosition(p, 8, 1);
    644     }
    645
    646     @Test
    647     void testDiscardWhitespace() {
    648         // arrange
    649         final SimpleTextParser p = parser("a\t\n\r\n   b c");
    650
    651         // act/assert
    652         p.discardWhitespace();
    653         assertPosition(p, 1, 1);
    654         assertChar('a', p.readChar());
    655
    656         p.discardWhitespace();
    657         assertPosition(p, 3, 4);
    658         assertChar('b', p.readChar());
    659
    660         p.discardWhitespace();
    661         assertPosition(p, 3, 6);
    662         assertChar('c', p.readChar());
    663
    664         p.discardWhitespace();
    665         assertPosition(p, 3, 7);
    666         assertChar(EOF, p.readChar());
    667     }
    668
    669     @Test
    670     void testDiscardLineWhitespace() {
    671         // arrange
    672         final SimpleTextParser p = parser("a\t\n\r\n   b c");
    673
    674         // act/assert
    675         p.discardLineWhitespace();
    676         assertPosition(p, 1, 1);
    677         assertChar('a', p.readChar());
    678
    679         p.discardLineWhitespace();
    680         assertPosition(p, 1, 3);
    681         assertChar('\n', p.peekChar());
    682
    683         p.discardLineWhitespace();
    684         assertPosition(p, 1, 3);
    685         assertChar('\n', p.readChar());
    686
    687         p.discardLineWhitespace();
    688         assertPosition(p, 2, 1);
    689         assertChar('\r', p.readChar());
    690
    691         p.discardLineWhitespace();
    692         assertPosition(p, 2, 2);
    693         assertChar('\n', p.readChar());
    694
    695         p.discardLineWhitespace();
    696         assertPosition(p, 3, 4);
    697         assertChar('b', p.readChar());
    698
    699         p.discardLineWhitespace();
    700         assertPosition(p, 3, 6);
    701         assertChar('c', p.readChar());
    702
    703         p.discardLineWhitespace();
    704         assertPosition(p, 3, 7);
    705         assertChar(EOF, p.readChar());
    706     }
    707
    708     @Test
    709     void testDiscardNewLineSequence() {
    710         // arrange
    711         final SimpleTextParser p = parser("a\t\n\r\n   b\rc");
    712
    713         // act/assert
    714         p.discardNewLineSequence();
    715         assertPosition(p, 1, 1);
    716         assertChar('a', p.readChar());
    717
    718         p.discardLineWhitespace();
    719
    720         p.discardNewLineSequence();
    721         assertPosition(p, 2, 1);
    722         assertChar('\r', p.readChar());
    723
    724         p.discardNewLineSequence();
    725         assertPosition(p, 3, 1);
    726         assertChar(' ', p.readChar());
    727
    728         p.discardWhitespace();
    729
    730         p.discardNewLineSequence();
    731         assertPosition(p, 3, 4);
    732         assertChar('b', p.readChar());
    733
    734         p.discardNewLineSequence();
    735         assertPosition(p, 4, 1);
    736         assertChar('c', p.readChar());
    737
    738         p.discardNewLineSequence();
    739         assertPosition(p, 4, 2);
    740         assertChar(EOF, p.readChar());
    741     }
    742
    743     @Test
    744     void testDiscardLine() {
    745         // arrange
    746         final SimpleTextParser p = parser("a\t\n\r\n   b c");
    747
    748         // act/assert
    749         p.discardLine();
    750         assertChar('\r', p.peekChar());
    751         assertPosition(p, 2, 1);
    752
    753         p.discardLine();
    754         assertChar(' ', p.peekChar());
    755         assertPosition(p, 3, 1);
    756
    757         p.discardLine();
    758         assertPosition(p, 3, 7);
    759         assertChar(EOF, p.peekChar());
    760
    761         p.discardLine();
    762         assertPosition(p, 3, 7);
    763         assertChar(EOF, p.peekChar());
    764     }
    765
    766     @Test
    767     void testPeek_lenArg() {
    768         // arrange
    769         final SimpleTextParser p = parser("abcdef\r\n\r ghi");
    770
    771         // act/assert
    772         Assertions.assertEquals("", p.peek(0));
    773         assertPosition(p, 1, 1);
    774
    775         Assertions.assertEquals("", p.peek(0));
    776         assertPosition(p, 1, 1);
    777
    778         p.readChar();
    779
    780         Assertions.assertEquals("bcde", p.peek(4));
    781         assertPosition(p, 1, 2);
    782
    783         Assertions.assertEquals("bcdef\r", p.peek(6));
    784         assertPosition(p, 1, 2);
    785
    786         Assertions.assertEquals("bcdef\r\n\r ghi", p.peek(100));
    787         assertPosition(p, 1, 2);
    788
    789         assertChar('b', p.readChar());
    790
    791         p.discard(c -> true);
    792
    793         Assertions.assertNull(p.peek(0));
    794         Assertions.assertNull(p.peek(100));
    795     }
    796
    797     @Test
    798     void testPeek_lenArg_invalidArg() {
    799         // arrange
    800         final SimpleTextParser p = parser("abcdef");
    801         p.setMaxStringLength(4);
    802
    803         // act/assert
    804         GeometryTestUtils.assertThrowsWithMessage(() -> {
    805             p.peek(-1);
    806         }, IllegalArgumentException.class, "Requested string length cannot be negative; was -1");
    807
    808         GeometryTestUtils.assertThrowsWithMessage(() -> {
    809             p.peek(6);
    810         }, IllegalArgumentException.class, "Requested string length of 6 exceeds maximum value of 4");
    811     }
    812
    813     @Test
    814     void testPeek_predicateArg() {
    815         // arrange
    816         final SimpleTextParser p = parser("abcdef\r\n\r ghi");
    817
    818         // act/assert
    819         Assertions.assertEquals("", p.peek(c -> false));
    820         assertPosition(p, 1, 1);
    821
    822         p.readChar();
    823
    824         Assertions.assertEquals("bcdef", p.peek(SimpleTextParser::isAlphanumeric));
    825         assertPosition(p, 1, 2);
    826
    827         Assertions.assertEquals("bcdef\r\n\r ghi", p.peek(c -> true));
    828         assertPosition(p, 1, 2);
    829
    830         assertChar('b', p.readChar());
    831
    832         p.discard(c -> true);
    833
    834         Assertions.assertNull(p.peek(c -> true));
    835         Assertions.assertNull(p.peek(c -> false));
    836     }
    837
    838     @Test
    839     void testPeek_predicateArg_exceedsMaxStringLength() {
    840         // arrange
    841         final SimpleTextParser p = parser("\n  abcdefg");
    842         p.setMaxStringLength(4);
    843         p.discardLine()
    844             .discard(SimpleTextParser::isWhitespace);
    845
    846         // act/assert
    847         GeometryTestUtils.assertThrowsWithMessage(() -> {
    848             p.peek(SimpleTextParser::isNotWhitespace);
    849         }, IllegalStateException.class, "Parsing failed at line 2, column 3: string length exceeds maximum value of 4");
    850     }
    851
    852     @Test
    853     void testMatch() {
    854         // arrange
    855         final SimpleTextParser p = parser("abcdef");
    856
    857         // act/assert
    858         p.next(1)
    859             .match("a")
    860             .next(100)
    861             .match("bcdef");
    862
    863         Assertions.assertFalse(p.hasMoreCharacters());
    864     }
    865
    866     @Test
    867     void testMatch_failure() {
    868         // arrange
    869         final SimpleTextParser p = parser("abcdef");
    870
    871         // act/assert
    872         GeometryTestUtils.assertThrowsWithMessage(() -> {
    873             p.match("empty");
    874         }, IllegalStateException.class, "No token has been read from the character stream");
    875
    876         p.next(1);
    877         GeometryTestUtils.assertThrowsWithMessage(() -> {
    878             p.match("b");
    879         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [b] but found [a]");
    880
    881         GeometryTestUtils.assertThrowsWithMessage(() -> {
    882             p.match("A");
    883         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [A] but found [a]");
    884
    885         GeometryTestUtils.assertThrowsWithMessage(() -> {
    886             p.match(null);
    887         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [null] but found [a]");
    888     }
    889
    890     @Test
    891     void testMatch_ignoreCase() {
    892         // arrange
    893         final SimpleTextParser p = parser("abcdef");
    894
    895         // act/assert
    896         p.next(1)
    897             .matchIgnoreCase("A")
    898             .next(100)
    899             .matchIgnoreCase("BcdEF");
    900
    901         Assertions.assertFalse(p.hasMoreCharacters());
    902     }
    903
    904     @Test
    905     void testMatchIgnoreCase_failure() {
    906         // arrange
    907         final SimpleTextParser p = parser("abcdef");
    908
    909         // act/assert
    910         GeometryTestUtils.assertThrowsWithMessage(() -> {
    911             p.matchIgnoreCase("empty");
    912         }, IllegalStateException.class, "No token has been read from the character stream");
    913
    914         p.next(1);
    915         GeometryTestUtils.assertThrowsWithMessage(() -> {
    916             p.matchIgnoreCase("b");
    917         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [b] but found [a]");
    918
    919         GeometryTestUtils.assertThrowsWithMessage(() -> {
    920             p.match(null);
    921         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected [null] but found [a]");
    922     }
    923
    924     @Test
    925     void testTryMatch() {
    926         // arrange
    927         final SimpleTextParser p = parser("abc");
    928
    929         // act/assert
    930         p.next(3);
    931
    932         Assertions.assertTrue(p.tryMatch("abc"));
    933
    934         Assertions.assertFalse(p.tryMatch("ab"));
    935         Assertions.assertFalse(p.tryMatch(""));
    936         Assertions.assertFalse(p.tryMatch(null));
    937
    938         Assertions.assertFalse(p.tryMatch("ABC"));
    939         Assertions.assertFalse(p.tryMatch("aBc"));
    940
    941         p.next(1);
    942         Assertions.assertTrue(p.tryMatch(null));
    943     }
    944
    945     @Test
    946     void testTryMatch_noToken() {
    947         // arrange
    948         final SimpleTextParser p = parser("abcdef");
    949
    950         // act/assert
    951         GeometryTestUtils.assertThrowsWithMessage(() -> {
    952             p.tryMatch("empty");
    953         }, IllegalStateException.class, "No token has been read from the character stream");
    954     }
    955
    956     @Test
    957     void testTryMatchIgnoreCase() {
    958         // arrange
    959         final SimpleTextParser p = parser("abc");
    960
    961         // act/assert
    962         p.next(3);
    963
    964         Assertions.assertTrue(p.tryMatchIgnoreCase("abc"));
    965         Assertions.assertTrue(p.tryMatchIgnoreCase("ABC"));
    966         Assertions.assertTrue(p.tryMatchIgnoreCase("aBc"));
    967
    968         Assertions.assertFalse(p.tryMatch("ab"));
    969         Assertions.assertFalse(p.tryMatch(""));
    970         Assertions.assertFalse(p.tryMatch(null));
    971
    972         p.next(1);
    973         Assertions.assertTrue(p.tryMatch(null));
    974     }
    975
    976     @Test
    977     void testTryMatchIgnoreCase_noToken() {
    978         // arrange
    979         final SimpleTextParser p = parser("abcdef");
    980
    981         // act/assert
    982         GeometryTestUtils.assertThrowsWithMessage(() -> {
    983             p.tryMatchIgnoreCase("empty");
    984         }, IllegalStateException.class, "No token has been read from the character stream");
    985     }
    986
    987     @Test
    988     void testChoose() {
    989         // arrange
    990         final SimpleTextParser p = parser("abc");
    991
    992         // act/assert
    993         p.next(1);
    994
    995         Assertions.assertEquals(0, p.choose("a"));
    996
    997         Assertions.assertEquals(0, p.choose("a", "b", "c"));
    998         Assertions.assertEquals(2, p.choose("c", "b", "a"));
    999
    1000         p.next(1);
    1001
    1002         Assertions.assertEquals(0, p.choose("b"));
    1003
    1004         Assertions.assertEquals(1, p.choose("a", "b", "c"));
    1005         Assertions.assertEquals(1, p.choose("c", "b", "a"));
    1006     }
    1007
    1008     @Test
    1009     void testChoose_failure() {
    1010         // arrange
    1011         final SimpleTextParser p = parser("abc");
    1012
    1013         // act/assert
    1014         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1015             p.choose("X");
    1016         }, IllegalStateException.class, "No token has been read from the character stream");
    1017
    1018         p.next(1);
    1019         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1020             p.choose("X");
    1021         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X] but found [a]");
    1022
    1023         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1024             p.choose("X", "Y", "Z");
    1025         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X, Y, Z] but found [a]");
    1026
    1027         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1028             p.choose("A");
    1029         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [A] but found [a]");
    1030
    1031         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1032             p.choose();
    1033         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [] but found [a]");
    1034     }
    1035
    1036     @Test
    1037     void testChooseIgnoreCase() {
    1038         // arrange
    1039         final SimpleTextParser p = parser("abc");
    1040
    1041         // act/assert
    1042         p.next(1);
    1043
    1044         Assertions.assertEquals(0, p.chooseIgnoreCase("A"));
    1045
    1046         Assertions.assertEquals(0, p.chooseIgnoreCase("A", "b", "C"));
    1047         Assertions.assertEquals(2, p.chooseIgnoreCase("C", "b", "A"));
    1048
    1049         p.next(1);
    1050
    1051         Assertions.assertEquals(0, p.chooseIgnoreCase("b"));
    1052
    1053         Assertions.assertEquals(1, p.chooseIgnoreCase("A", "b", "C"));
    1054         Assertions.assertEquals(1, p.chooseIgnoreCase("C", "b", "A"));
    1055     }
    1056
    1057     @Test
    1058     void testChooseIgnoreCase_failure() {
    1059         // arrange
    1060         final SimpleTextParser p = parser("abc");
    1061
    1062         // act/assert
    1063         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1064             p.chooseIgnoreCase("X");
    1065         }, IllegalStateException.class, "No token has been read from the character stream");
    1066
    1067         p.next(1);
    1068         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1069             p.chooseIgnoreCase("X");
    1070         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X] but found [a]");
    1071
    1072         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1073             p.chooseIgnoreCase("X", "Y", "Z");
    1074         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [X, Y, Z] but found [a]");
    1075
    1076         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1077             p.chooseIgnoreCase();
    1078         }, IllegalStateException.class, "Parsing failed at line 1, column 1: expected one of [] but found [a]");
    1079     }
    1080
    1081     @Test
    1082     void testTryChoose() {
    1083         // arrange
    1084         final SimpleTextParser p = parser("abc");
    1085
    1086         // act/assert
    1087         p.next(1);
    1088
    1089         Assertions.assertEquals(0, p.tryChoose("a"));
    1090
    1091         Assertions.assertEquals(0, p.tryChoose("a", "b", "c"));
    1092         Assertions.assertEquals(2, p.tryChoose("c", "b", "a"));
    1093
    1094         p.next(1);
    1095
    1096         Assertions.assertEquals(0, p.tryChoose("b"));
    1097
    1098         Assertions.assertEquals(1, p.tryChoose("a", "b", "c"));
    1099         Assertions.assertEquals(1, p.tryChoose("c", "b", "a"));
    1100
    1101         Assertions.assertEquals(-1, p.tryChoose("A", "B", "C"));
    1102         Assertions.assertEquals(-1, p.tryChoose());
    1103         Assertions.assertEquals(-1, p.tryChoose((String) null));
    1104     }
    1105
    1106     @Test
    1107     void testTryChoose_noToken() {
    1108         // arrange
    1109         final SimpleTextParser p = parser("abcdef");
    1110
    1111         // act/assert
    1112         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1113             p.tryChoose("X");
    1114         }, IllegalStateException.class, "No token has been read from the character stream");
    1115     }
    1116
    1117     @Test
    1118     void testTryChooseIgnoreCase() {
    1119         // arrange
    1120         final SimpleTextParser p = parser("abc");
    1121
    1122         // act/assert
    1123         p.next(1);
    1124
    1125         Assertions.assertEquals(0, p.tryChooseIgnoreCase("a"));
    1126
    1127         Assertions.assertEquals(0, p.tryChooseIgnoreCase("A", "B", "C"));
    1128         Assertions.assertEquals(2, p.tryChooseIgnoreCase("C", "b", "A"));
    1129
    1130         p.next(1);
    1131
    1132         Assertions.assertEquals(0, p.tryChooseIgnoreCase("B"));
    1133
    1134         Assertions.assertEquals(1, p.tryChooseIgnoreCase("a", "B", "c"));
    1135         Assertions.assertEquals(1, p.tryChooseIgnoreCase("c", "b", "a"));
    1136
    1137         Assertions.assertEquals(-1, p.tryChooseIgnoreCase("X", "Y", "Z"));
    1138         Assertions.assertEquals(-1, p.tryChooseIgnoreCase());
    1139         Assertions.assertEquals(-1, p.tryChooseIgnoreCase((String) null));
    1140     }
    1141
    1142     @Test
    1143     void testTryChooseIgnoreCase_noToken() {
    1144         // arrange
    1145         final SimpleTextParser p = parser("abcdef");
    1146
    1147         // act/assert
    1148         GeometryTestUtils.assertThrowsWithMessage(() -> {
    1149             p.tryChooseIgnoreCase("X");
    1150         }, IllegalStateException.class, "No token has been read from the character stream");
    1151     }
    1152
    1153     @Test
    1154     void testUnexpectedToken() {
    1155         // arrange
    1156         final SimpleTextParser p = parser("abc\ndef");
    1157
    1158         // act/assert
    1159         Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found no current token",
    1160                 p.unexpectedToken("test").getMessage());
    1161
    1162         p.nextAlphanumeric();
    1163         Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found [abc]",
    1164                 p.unexpectedToken("test").getMessage());
    1165
    1166         p.nextAlphanumeric();
    1167         Assertions.assertEquals("Parsing failed at line 1, column 4: expected test but found end of line",
    1168                 p.unexpectedToken("test").getMessage());
    1169
    1170         p.discardLine();
    1171
    1172         p.next(SimpleTextParser::isWhitespace);
    1173         Assertions.assertEquals("Parsing failed at line 2, column 1: expected test but found empty token followed by [d]",
    1174                 p.unexpectedToken("test").getMessage());
    1175
    1176         p.next(3).next(10);
    1177         Assertions.assertEquals("Parsing failed at line 2, column 4: expected test but found end of content",
    1178                 p.unexpectedToken("test").getMessage());
    1179     }
    1180
    1181     @Test
    1182     void testUnexpectedToken_causeArg() {
    1183         // arrange
    1184         final SimpleTextParser p = parser("abc");
    1185         final Exception cause = new Exception("test");
    1186
    1187         // act/assert
    1188         p.nextLine();
    1189
    1190         final IllegalStateException exc = p.unexpectedToken("test", cause);
    1191         Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found [abc]",
    1192                 exc.getMessage());
    1193         Assertions.assertSame(cause, exc.getCause());
    1194     }
    1195
    1196     @Test
    1197     void testUnexpectedToken_ioError() {
    1198         // arrange
    1199         final FailBuffer b = new FailBuffer(new StringReader("abc"));
    1200         final SimpleTextParser p = new SimpleTextParser(b);
    1201
    1202         // act/assert
    1203         b.setFail(false);
    1204         p.next(SimpleTextParser::isDecimalPart);
    1205         b.setFail(true);
    1206         Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found empty token",
    1207                 p.unexpectedToken("test").getMessage());
    1208
    1209         b.setFail(false);
    1210         p.nextAlphanumeric();
    1211         b.setFail(true);
    1212         Assertions.assertEquals("Parsing failed at line 1, column 1: expected test but found [abc]",
    1213                 p.unexpectedToken("test").getMessage());
    1214
    1215         b.setFail(false);
    1216         p.nextAlphanumeric();
    1217         b.setFail(true);
    1218         Assertions.assertEquals("Parsing failed at line 1, column 4: expected test but found no current token",
    1219                 p.unexpectedToken("test").getMessage());
    1220     }
    1221
    1222     @Test
    1223     void testTokenError() {
    1224         // arrange
    1225         final SimpleTextParser p = parser("a\nbc");
    1226         p.nextLine();
    1227         p.next(1);
    1228         p.readChar();
    1229
    1230         // act/assert
    1231         final IllegalStateException exc = p.tokenError("test message");
    1232
    1233         Assertions.assertEquals("Parsing failed at line 2, column 1: test message", exc.getMessage());
    1234         Assertions.assertNull(exc.getCause());
    1235     }
    1236
    1237     @Test
    1238     void testTokenError_noTokenSet() {
    1239         // arrange
    1240         final SimpleTextParser p = parser("ab\nc");
    1241         p.readChar();
    1242
    1243         // act/assert
    1244         final IllegalStateException exc = p.tokenError("test message");
    1245
    1246         Assertions.assertEquals("Parsing failed at line 1, column 2: test message", exc.getMessage());
    1247         Assertions.assertNull(exc.getCause());
    1248     }
    1249
    1250     @Test
    1251     void testTokenError_withCause() {
    1252         // arrange
    1253         SimpleTextParser p = parser("a\nbc");
    1254         p.nextLine();
    1255         p.next(1);
    1256         p.readChar();
    1257
    1258         final Exception cause = new Exception("test");
    1259
    1260         // act/assert
    1261         final IllegalStateException exc = p.tokenError("test message", cause);
    1262
    1263         Assertions.assertEquals("Parsing failed at line 2, column 1: test message", exc.getMessage());
    1264         Assertions.assertSame(cause, exc.getCause());
    1265     }
    1266
    1267     @Test
    1268     void testParseError_currentLineCol() {
    1269         // arrange
    1270         final SimpleTextParser p = parser("a\nbc");
    1271         p.discard(ch -> ch != 'b');
    1272
    1273         // act
    1274         final IllegalStateException exc = p.parseError("test message");
    1275
    1276         Assertions.assertEquals("Parsing failed at line 2, column 1: test message", exc.getMessage());
    1277         Assertions.assertNull(exc.getCause());
    1278     }
    1279
    1280     @Test
    1281     void testParseError_currentLineCol_withCause() {
    1282         // arrange
    1283         final SimpleTextParser p = parser("abc");
    1284         p.readChar();
    1285         final Exception cause = new Exception("test");
    1286
    1287         // act
    1288         final IllegalStateException exc = p.parseError("test message", cause);
    1289
    1290         Assertions.assertEquals("Parsing failed at line 1, column 2: test message", exc.getMessage());
    1291         Assertions.assertSame(cause, exc.getCause());
    1292     }
    1293
    1294     @Test
    1295     void testParseError_givenLineCol() {
    1296         // arrange
    1297         final SimpleTextParser p = parser("abc");
    1298
    1299         // act
    1300         final IllegalStateException exc = p.parseError(5, 6, "test message");
    1301
    1302         Assertions.assertEquals("Parsing failed at line 5, column 6: test message", exc.getMessage());
    1303         Assertions.assertNull(exc.getCause());
    1304     }
    1305
    1306     @Test
    1307     void testParseError_givenLineCol_withCause() {
    1308         // arrange
    1309         final SimpleTextParser p = parser("abc");
    1310         final Exception cause = new Exception("test");
    1311
    1312         // act
    1313         final IllegalStateException exc = p.parseError(5, 6, "test message", cause);
    1314
    1315         Assertions.assertEquals("Parsing failed at line 5, column 6: test message", exc.getMessage());
    1316         Assertions.assertSame(cause, exc.getCause());
    1317     }
    1318
    1319     @Test
    1320     void testCharacterPredicates() {
    1321         // act/assert
    1322         assertMatchesAll(SimpleTextParser::isWhitespace, " \t\n\r");
    1323         assertDoesNotMatchAny(SimpleTextParser::isWhitespace, "abcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
    1324
    1325         assertMatchesAll(SimpleTextParser::isNotWhitespace, "abcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
    1326         assertDoesNotMatchAny(SimpleTextParser::isNotWhitespace, " \t\n\r");
    1327
    1328         assertMatchesAll(SimpleTextParser::isLineWhitespace, " \t");
    1329         assertDoesNotMatchAny(SimpleTextParser::isLineWhitespace, "\n\rabcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
    1330
    1331         assertMatchesAll(SimpleTextParser::isNewLinePart, "\n\r");
    1332         assertDoesNotMatchAny(SimpleTextParser::isNewLinePart, " \tabcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
    1333
    1334         assertMatchesAll(SimpleTextParser::isNotNewLinePart, " \tabcABC<>,./?:;'\"[]{}`~!@#$%^&*()_+-=");
    1335         assertDoesNotMatchAny(SimpleTextParser::isNotNewLinePart, "\n\r");
    1336
    1337         assertMatchesAll(SimpleTextParser::isAlphanumeric, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
    1338         assertDoesNotMatchAny(SimpleTextParser::isAlphanumeric, " \t\n\r./?:;'\\\"[]{}`~!@#$%^&*()_+-=");
    1339
    1340         assertMatchesAll(SimpleTextParser::isNotAlphanumeric, " \t\n\r./?:;'\\\"[]{}`~!@#$%^&*()_+-=");
    1341         assertDoesNotMatchAny(SimpleTextParser::isNotAlphanumeric, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
    1342
    1343         assertMatchesAll(SimpleTextParser::isIntegerPart, "0123456789+-");
    1344         assertDoesNotMatchAny(SimpleTextParser::isIntegerPart, " \t\n\r./?:;'\\\"[]{}`~!@#$%^&*()_=abcdeABCDE");
    1345
    1346         assertMatchesAll(SimpleTextParser::isDecimalPart, "0123456789+-.eE");
    1347         assertDoesNotMatchAny(SimpleTextParser::isDecimalPart, " \t\n\r/?:;'\\\"[]{}`~!@#$%^&*()_=abcdABCD");
    1348     }
    1349
    1350     private static SimpleTextParser parser(final String content) {
    1351         final StringReader reader = new StringReader(content);
    1352
    1353         return new SimpleTextParser(reader);
    1354     }
    1355
    1356     private static void assertCharacterSequence(final SimpleTextParser parser, final String expected) {
    1357         char expectedChar;
    1358         String msg;
    1359         for (int i = 0; i < expected.length(); ++i) {
    1360             expectedChar = expected.charAt(i);
    1361
    1362             msg = "Failed at index " + i + ":";
    1363
    1364             Assertions.assertEquals(expectedChar, parser.peekChar(), msg);
    1365             Assertions.assertEquals(expectedChar, parser.peekChar(), msg);
    1366
    1367             Assertions.assertTrue(parser.hasMoreCharacters());
    1368             Assertions.assertEquals(expectedChar, parser.readChar(), msg);
    1369         }
    1370
    1371         Assertions.assertFalse(parser.hasMoreCharacters());
    1372         Assertions.assertEquals(-1, parser.peekChar());
    1373         Assertions.assertEquals(-1, parser.peekChar());
    1374         Assertions.assertEquals(-1, parser.readChar());
    1375     }
    1376
    1377     private static void assertChar(final int expected, final int actual) {
    1378         final String expectedStr = describeChar(expected);
    1379         final String actualStr = describeChar(actual);
    1380
    1381         Assertions.assertEquals(expected, actual, "Expected [" + expectedStr + "] but was [" + actualStr + "];");
    1382     }
    1383
    1384     private static void assertMatchesAll(final IntPredicate pred, final String chars) {
    1385         for (char ch : chars.toCharArray()) {
    1386             final String msg = "Expected predicate to match [" + describeChar(ch) + "]";
    1387             Assertions.assertTrue(pred.test(ch), msg);
    1388         }
    1389     }
    1390
    1391     private static void assertDoesNotMatchAny(final IntPredicate pred, final String chars) {
    1392         for (char ch : chars.toCharArray()) {
    1393             final String msg = "Expected predicate to not match [" + describeChar(ch) + "]";
    1394             Assertions.assertFalse(pred.test(ch), msg);
    1395         }
    1396     }
    1397
    1398     private static String describeChar(final int ch) {
    1399         switch (ch) {
    1400         case '\n':
    1401             return "\\n";
    1402         case '\r':
    1403             return "\\r";
    1404         case '\t':
    1405             return "\\t";
    1406         case EOF:
    1407             return "EOF";
    1408         default:
    1409             return String.valueOf((char) ch);
    1410         }
    1411     }
    1412
    1413     private static void assertPosition(final SimpleTextParser parser, final int line, final int col) {
    1414         Assertions.assertEquals(line, parser.getLineNumber(), "Unexpected line number");
    1415         Assertions.assertEquals(col, parser.getColumnNumber(), "Unexpected column number");
    1416     }
    1417
    1418     private static void assertToken(final SimpleTextParser parser, final String token, final int line, final int col) {
    1419         Assertions.assertEquals(token, parser.getCurrentToken(), "Unexpected token");
    1420         Assertions.assertEquals(line, parser.getCurrentTokenLineNumber(), "Unexpected token line number");
    1421         Assertions.assertEquals(col, parser.getCurrentTokenColumnNumber(), "Unexpected token column number");
    1422     }
    1423
    1424     private static final class FailBuffer extends CharReadBuffer {
    1425
    1426         private boolean fail;
    1427
    1428         FailBuffer(final Reader in) {
    1429             super(in);
    1430         }
    1431
    1432         public void setFail(final boolean fail) {
    1433             this.fail = fail;
    1434         }
    1435
    1436         @Override
    1437         public boolean hasMoreCharacters() {
    1438             checkFail();
    1439             return super.hasMoreCharacters();
    1440         }
    1441
    1442         private void checkFail() {
    1443             if (fail) {
    1444                 throw new IllegalStateException("test failure");
    1445             }
    1446         }
    1447     }
    1448 }