| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'package:string_scanner/src/charcode.dart'; | |
| import 'package:string_scanner/string_scanner.dart'; | |
| import 'package:test/test.dart'; | |
| void main() { | |
| late LineScanner scanner; | |
| setUp(() { | |
| scanner = LineScanner('foo\nbar\r\nbaz'); | |
| }); | |
| test('begins with line and column 0', () { | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| group('scan()', () { | |
| test('consuming no newlines increases the column but not the line', () { | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| }); | |
| test('consuming a LF resets the column and increases the line', () { | |
| scanner.expect('foo\nba'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('consuming multiple LFs resets the column and increases the line', () { | |
| scanner.expect('foo\nbar\r\nb'); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('consuming a CR LF increases the line only after the LF', () { | |
| scanner.expect('foo\nbar\r'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| scanner.expect('\nb'); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('consuming a CR not followed by LF increases the line', () { | |
| scanner = LineScanner('foo\nbar\rbaz'); | |
| scanner.expect('foo\nbar\r'); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| scanner.expect('b'); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('consuming a CR at the end increases the line', () { | |
| scanner = LineScanner('foo\nbar\r'); | |
| scanner.expect('foo\nbar\r'); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| expect(scanner.isDone, isTrue); | |
| }); | |
| test('consuming a mix of CR, LF, CR+LF increases the line', () { | |
| scanner = LineScanner('0\n1\r2\r\n3'); | |
| scanner.expect('0\n1\r2\r\n3'); | |
| expect(scanner.line, equals(3)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('scanning a zero length match between CR LF does not fail', () { | |
| scanner.expect('foo\nbar\r'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| scanner.expect(RegExp('(?!x)')); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| }); | |
| }); | |
| group('readChar()', () { | |
| test('on a non-newline character increases the column but not the line', | |
| () { | |
| scanner.readChar(); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('consuming a LF resets the column and increases the line', () { | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| scanner.readChar(); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a CR LF increases the line only after the LF', () { | |
| scanner = LineScanner('foo\r\nbar'); | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| scanner.readChar(); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(4)); | |
| scanner.readChar(); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a CR not followed by a LF increases the line', () { | |
| scanner = LineScanner('foo\nbar\rbaz'); | |
| scanner.expect('foo\nbar'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(3)); | |
| scanner.readChar(); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a CR at the end increases the line', () { | |
| scanner = LineScanner('foo\nbar\r'); | |
| scanner.expect('foo\nbar'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(3)); | |
| scanner.readChar(); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a mix of CR, LF, CR+LF increases the line', () { | |
| scanner = LineScanner('0\n1\r2\r\n3'); | |
| for (var i = 0; i < scanner.string.length; i++) { | |
| scanner.readChar(); | |
| } | |
| expect(scanner.line, equals(3)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| }); | |
| group('readCodePoint()', () { | |
| test('on a non-newline character increases the column but not the line', | |
| () { | |
| scanner.readCodePoint(); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('consuming a newline resets the column and increases the line', () { | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| scanner.readCodePoint(); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test("consuming halfway through a CR LF doesn't count as a line", () { | |
| scanner.expect('foo\nbar'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(3)); | |
| scanner.readCodePoint(); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| scanner.readCodePoint(); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| }); | |
| group('scanChar()', () { | |
| test('on a non-newline character increases the column but not the line', | |
| () { | |
| scanner.scanChar($f); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('consuming a LF resets the column and increases the line', () { | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| scanner.scanChar($lf); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a CR LF increases the line only after the LF', () { | |
| scanner.expect('foo\nbar'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(3)); | |
| scanner.scanChar($cr); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| scanner.scanChar($lf); | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a CR not followed by LF increases the line', () { | |
| scanner = LineScanner('foo\rbar'); | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| scanner.scanChar($cr); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a CR at the end increases the line', () { | |
| scanner = LineScanner('foo\r'); | |
| scanner.expect('foo'); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(3)); | |
| scanner.scanChar($cr); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('consuming a mix of CR, LF, CR+LF increases the line', () { | |
| scanner = LineScanner('0\n1\r2\r\n3'); | |
| for (var i = 0; i < scanner.string.length; i++) { | |
| scanner.scanChar(scanner.string[i].codeUnits.single); | |
| } | |
| expect(scanner.line, equals(3)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| }); | |
| group('before a surrogate pair', () { | |
| final codePoint = '\uD83D\uDC6D'.runes.first; | |
| const highSurrogate = 0xD83D; | |
| late LineScanner scanner; | |
| setUp(() { | |
| scanner = LineScanner('foo: \uD83D\uDC6D'); | |
| expect(scanner.scan('foo: '), isTrue); | |
| }); | |
| test('readChar returns the high surrogate and moves into the pair', () { | |
| expect(scanner.readChar(), equals(highSurrogate)); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(6)); | |
| expect(scanner.position, equals(6)); | |
| }); | |
| test('readCodePoint returns the code unit and moves past the pair', () { | |
| expect(scanner.readCodePoint(), equals(codePoint)); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(7)); | |
| expect(scanner.position, equals(7)); | |
| }); | |
| test('scanChar with the high surrogate moves into the pair', () { | |
| expect(scanner.scanChar(highSurrogate), isTrue); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(6)); | |
| expect(scanner.position, equals(6)); | |
| }); | |
| test('scanChar with the code point moves past the pair', () { | |
| expect(scanner.scanChar(codePoint), isTrue); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(7)); | |
| expect(scanner.position, equals(7)); | |
| }); | |
| test('expectChar with the high surrogate moves into the pair', () { | |
| scanner.expectChar(highSurrogate); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(6)); | |
| expect(scanner.position, equals(6)); | |
| }); | |
| test('expectChar with the code point moves past the pair', () { | |
| scanner.expectChar(codePoint); | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(7)); | |
| expect(scanner.position, equals(7)); | |
| }); | |
| }); | |
| group('position=', () { | |
| test('forward through LFs sets the line and column', () { | |
| scanner = LineScanner('foo\nbar\nbaz'); | |
| scanner.position = 9; // "foo\nbar\nb" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('forward from non-zero character through LFs sets the line and column', | |
| () { | |
| scanner = LineScanner('foo\nbar\nbaz'); | |
| scanner.expect('fo'); | |
| scanner.position = 9; // "foo\nbar\nb" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('forward through CR LFs sets the line and column', () { | |
| scanner = LineScanner('foo\r\nbar\r\nbaz'); | |
| scanner.position = 11; // "foo\r\nbar\r\nb" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('forward through CR not followed by LFs sets the line and column', () { | |
| scanner = LineScanner('foo\rbar\rbaz'); | |
| scanner.position = 9; // "foo\rbar\rb" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('forward through CR at end sets the line and column', () { | |
| scanner = LineScanner('foo\rbar\r'); | |
| scanner.position = 8; // "foo\rbar\r" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('forward through a mix of CR, LF, CR+LF sets the line and column', () { | |
| scanner = LineScanner('0\n1\r2\r\n3'); | |
| scanner.position = scanner.string.length; | |
| expect(scanner.line, equals(3)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('forward through no newlines sets the column', () { | |
| scanner.position = 2; // "fo" | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('backward through LFs sets the line and column', () { | |
| scanner = LineScanner('foo\nbar\nbaz'); | |
| scanner.expect('foo\nbar\nbaz'); | |
| scanner.position = 2; // "fo" | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('backward through CR LFs sets the line and column', () { | |
| scanner = LineScanner('foo\r\nbar\r\nbaz'); | |
| scanner.expect('foo\r\nbar\r\nbaz'); | |
| scanner.position = 2; // "fo" | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('backward through CR not followed by LFs sets the line and column', | |
| () { | |
| scanner = LineScanner('foo\rbar\rbaz'); | |
| scanner.expect('foo\rbar\rbaz'); | |
| scanner.position = 2; // "fo" | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('backward through CR at end sets the line and column', () { | |
| scanner = LineScanner('foo\rbar\r'); | |
| scanner.expect('foo\rbar\r'); | |
| scanner.position = 2; // "fo" | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('backward through a mix of CR, LF, CR+LF sets the line and column', | |
| () { | |
| scanner = LineScanner('0\n1\r2\r\n3'); | |
| scanner.expect(scanner.string); | |
| scanner.position = 1; | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('backward through no newlines sets the column', () { | |
| scanner.expect('foo\nbar\r\nbaz'); | |
| scanner.position = 10; // "foo\nbar\r\nb" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test("forward halfway through a CR LF doesn't count as a line", () { | |
| scanner.position = 8; // "foo\nbar\r" | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| }); | |
| test('forward from halfway through a CR LF counts as a line', () { | |
| scanner.expect('foo\nbar\r'); | |
| scanner.position = 11; // "foo\nbar\r\nba" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(2)); | |
| }); | |
| test('backward to between CR LF', () { | |
| scanner.expect('foo\nbar\r\nbaz'); | |
| scanner.position = 8; // "foo\nbar\r" | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| }); | |
| test('backward from between CR LF', () { | |
| scanner.expect('foo\nbar\r'); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(4)); | |
| scanner.position = 5; // "foo\nb" | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('backward from between CR LF to before CR LF', () { | |
| scanner.expect('foo\nbar\r'); | |
| scanner.position = 1; // "f" | |
| expect(scanner.line, equals(0)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('backward to after CR LF', () { | |
| scanner.expect('foo\nbar\r\nbaz'); | |
| scanner.position = 9; // "foo\nbar\r\n" | |
| expect(scanner.line, equals(2)); | |
| expect(scanner.column, equals(0)); | |
| }); | |
| test('backward to before CR LF', () { | |
| scanner.expect('foo\nbar\r\nbaz'); | |
| scanner.position = 7; // "foo\nbar" | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(3)); | |
| }); | |
| }); | |
| test('state= restores the line, column, and position', () { | |
| scanner.expect('foo\nb'); | |
| final state = scanner.state; | |
| scanner.scan('ar\nba'); | |
| scanner.state = state; | |
| expect(scanner.rest, equals('ar\r\nbaz')); | |
| expect(scanner.line, equals(1)); | |
| expect(scanner.column, equals(1)); | |
| }); | |
| test('state= rejects a foreign state', () { | |
| scanner.scan('foo\nb'); | |
| expect(() => LineScanner(scanner.string).state = scanner.state, | |
| throwsArgumentError); | |
| }); | |
| } |