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 }