// 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 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); }); }