blob: 1af5c366650fbc472772b5e882ca33ad616e55d8 [file] [log] [blame]
// 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);
});
}